diff --git a/mycore-base/pom.xml b/mycore-base/pom.xml index acae9a6f09..84ebc9ae26 100644 --- a/mycore-base/pom.xml +++ b/mycore-base/pom.xml @@ -401,6 +401,11 @@ xalan xalan + + de.thetaphi + forbiddenapis + provided + org.apache.logging.log4j log4j-slf4j-impl diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatter.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatter.java new file mode 100644 index 0000000000..bf876e016b --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatter.java @@ -0,0 +1,33 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.date; + +import java.time.Instant; +import java.util.Date; + +/** + * A {@link MCRDateFormatter} implements a date formatting strategy. + */ +public sealed interface MCRDateFormatter permits MCRDateFormatterBase, MCRInstantFormatterBase { + + String format(Date date); + + String format(Instant instant); + +} diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterBase.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterBase.java new file mode 100644 index 0000000000..a3e73a76c8 --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterBase.java @@ -0,0 +1,38 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.date; + +import java.time.Instant; +import java.util.Date; + +/** + * {@link MCRDateFormatterBase} is a base implementation of {@link MCRDateFormatter} that + * formats {@link Date} objects. + */ +public non-sealed abstract class MCRDateFormatterBase implements MCRDateFormatter { + + @Override + public abstract String format(Date date); + + @Override + public final String format(Instant instant) { + return format(Date.from(instant)); + } + +} diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterUtils.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterUtils.java new file mode 100644 index 0000000000..5cfcfa0b3b --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterUtils.java @@ -0,0 +1,49 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.date; + +import java.time.ZoneId; +import java.util.Locale; + +/** + * Utility class providing functions commonly used by implementations of {@link MCRDateFormatter}. + */ +public final class MCRDateFormatterUtils { + + private MCRDateFormatterUtils() { + } + + public static Locale getLocale(String locale) { + return switch (locale) { + case null -> Locale.getDefault(); + case "ROOT" -> Locale.ROOT; + case "DEFAULT" -> Locale.getDefault(); + case String languageTag -> Locale.forLanguageTag(languageTag); + }; + } + + public static ZoneId getTimeZone(String timeZone) { + return switch (timeZone) { + case null -> ZoneId.systemDefault(); + case "DEFAULT" -> ZoneId.systemDefault(); + case String zoneId -> ZoneId.of(zoneId); + }; + } + +} diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateStyler.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateStyler.java new file mode 100644 index 0000000000..04ac43710a --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateStyler.java @@ -0,0 +1,114 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.date; + +import static org.mycore.common.date.MCRDateFormatterUtils.getLocale; +import static org.mycore.common.date.MCRDateFormatterUtils.getTimeZone; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.time.temporal.TemporalAccessor; +import java.util.Locale; +import java.util.Objects; +import java.util.function.Supplier; + +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; + +import de.thetaphi.forbiddenapis.SuppressForbidden; + +/** + * A {@link MCRDateStyler} is a {@link MCRDateFormatter} that uses + * {@link DateTimeFormatter#format(TemporalAccessor)}, instantiated with + * {@link DateTimeFormatter#ofLocalizedDateTime(FormatStyle)}, to format a date. + *

+ * The following configuration options are available: + *

    + *
  • The property suffix {@link MCRDateStyler#DATE_STYLE_KEY} can be used to + * specify the date style. + *
  • The property suffix {@link MCRDateStyler#LOCALE_KEY} can be used to + * specify the locale (optional, defaults to {@link Locale#getDefault()}; + * special values DEFAULT and ROOT can be used to set + * {@link Locale#getDefault()} and {@link Locale#ROOT} respectively). + *
  • The property suffix {@link MCRDateStyler#TIME_ZONE_KEY}can be used to + * specify the timezone (optional, defaults to {@link ZoneId#systemDefault()}; + * special value DEFAULT can be used to set {@link ZoneId#systemDefault()}). + *
+ * Example: + *

+ * [...].Class=org.mycore.common.date.MCRDateStyler
+ * [...].Format=yyyy-MM-dd'T'HH:mm
+ * [...].Locale=de_DE
+ * [...].TimeZone=Europe/Berlin
+ * 
+ */ +@MCRConfigurationProxy(proxyClass = MCRDateStyler.Factory.class) +public final class MCRDateStyler extends MCRInstantFormatterBase { + + public static final String DATE_STYLE_KEY = "DateStyle"; + + public static final String LOCALE_KEY = "Locale"; + + public static final String TIME_ZONE_KEY = "TimeZone"; + + private final DateTimeFormatter formatter; + + public MCRDateStyler(FormatStyle dateStyle) { + this(dateStyle, Locale.getDefault(), ZoneId.systemDefault()); + } + + public MCRDateStyler(FormatStyle dateStyle, Locale locale) { + this(dateStyle, locale, ZoneId.systemDefault()); + } + + @SuppressForbidden + public MCRDateStyler(FormatStyle dateStyle, Locale locale, ZoneId zoneId) { + Objects.requireNonNull(dateStyle, "Date style must not be null"); + Objects.requireNonNull(locale, "Locale must not be null"); + Objects.requireNonNull(zoneId, "Zone ID must not be null"); + this.formatter = DateTimeFormatter.ofLocalizedDate(dateStyle).localizedBy(locale).withZone(zoneId); + } + + @Override + public String format(Instant instant) { + + return formatter.format(instant); + } + + public static final class Factory implements Supplier { + + @MCRProperty(name = DATE_STYLE_KEY) + public String dateStyle; + + @MCRProperty(name = LOCALE_KEY, required = false) + public String locale; + + @MCRProperty(name = TIME_ZONE_KEY, required = false) + public String timeZone; + + @Override + public MCRDateStyler get() { + return new MCRDateStyler(FormatStyle.valueOf(dateStyle), getLocale(locale), getTimeZone(timeZone)); + } + + } + +} diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeFormatter.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeFormatter.java new file mode 100644 index 0000000000..062b1f6ad2 --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeFormatter.java @@ -0,0 +1,110 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.date; + +import static org.mycore.common.date.MCRDateFormatterUtils.getLocale; +import static org.mycore.common.date.MCRDateFormatterUtils.getTimeZone; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.util.Locale; +import java.util.Objects; +import java.util.function.Supplier; + +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; + +/** + * A {@link MCRDateTimeFormatter} is a {@link MCRDateFormatter} that uses + * {@link DateTimeFormatter#format(TemporalAccessor)}, instantiated with + * * {@link DateTimeFormatter#ofPattern(String)}, to format a date. + *

+ * The following configuration options are available: + *

    + *
  • The property suffix {@link MCRDateTimeFormatter#FORMAT_KEY} can be used to + * specify the format. + *
  • The property suffix {@link MCRDateTimeFormatter#LOCALE_KEY} can be used to + * specify the locale (optional, defaults to {@link Locale#getDefault()}; + * special values DEFAULT and ROOT can be used to set + * {@link Locale#getDefault()} and {@link Locale#ROOT} respectively). + *
  • The property suffix {@link MCRDateTimeFormatter#TIME_ZONE_KEY}can be used to + * specify the timezone (optional, defaults to {@link ZoneId#systemDefault()}; + * special value DEFAULT can be used to set {@link ZoneId#systemDefault()}). + *
+ * Example: + *

+ * [...].Class=org.mycore.common.date.MCRDateTimeFormatter
+ * [...].Format=yyyy-MM-dd'T'HH:mm
+ * [...].Locale=de_DE
+ * [...].TimeZone=Europe/Berlin
+ * 
+ */ +@MCRConfigurationProxy(proxyClass = MCRDateTimeFormatter.Factory.class) +public final class MCRDateTimeFormatter extends MCRInstantFormatterBase { + + public static final String FORMAT_KEY = "Format"; + + public static final String LOCALE_KEY = "Locale"; + + public static final String TIME_ZONE_KEY = "TimeZone"; + + private final DateTimeFormatter formatter; + + public MCRDateTimeFormatter(String format) { + this(format, Locale.getDefault(), ZoneId.systemDefault()); + } + + public MCRDateTimeFormatter(String format, Locale locale) { + this(format, locale, ZoneId.systemDefault()); + } + + public MCRDateTimeFormatter(String format, Locale locale, ZoneId zoneId) { + Objects.requireNonNull(format, "Format style must not be null"); + Objects.requireNonNull(locale, "Locale must not be null"); + Objects.requireNonNull(zoneId, "Zone ID must not be null"); + this.formatter = DateTimeFormatter.ofPattern(format, locale).withZone(zoneId); + } + + @Override + public String format(Instant instant) { + + return formatter.format(instant); + } + + public static final class Factory implements Supplier { + + @MCRProperty(name = FORMAT_KEY) + public String format; + + @MCRProperty(name = LOCALE_KEY, required = false) + public String locale; + + @MCRProperty(name = TIME_ZONE_KEY, required = false) + public String timeZone; + + @Override + public MCRDateTimeFormatter get() { + return new MCRDateTimeFormatter(format, getLocale(locale), getTimeZone(timeZone)); + } + + } + +} diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeStyler.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeStyler.java new file mode 100644 index 0000000000..87b2a74ea4 --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeStyler.java @@ -0,0 +1,123 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.date; + +import static org.mycore.common.date.MCRDateFormatterUtils.getLocale; +import static org.mycore.common.date.MCRDateFormatterUtils.getTimeZone; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.time.temporal.TemporalAccessor; +import java.util.Locale; +import java.util.Objects; +import java.util.function.Supplier; + +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; + +import de.thetaphi.forbiddenapis.SuppressForbidden; + +/** + * A {@link MCRDateTimeStyler} is a {@link MCRDateFormatter} that uses + * {@link DateTimeFormatter#format(TemporalAccessor)}, instantiated with + * {@link DateTimeFormatter#ofLocalizedDateTime(FormatStyle, FormatStyle)}, to format a date. + *

+ * The following configuration options are available: + *

    + *
  • The property suffix {@link MCRDateTimeStyler#DATE_STYLE_KEY} can be used to + * specify the date style. + *
  • The property suffix {@link MCRDateTimeStyler#TIME_STYLE_KEY} can be used to + * specify the time style. + *
  • The property suffix {@link MCRDateTimeStyler#LOCALE_KEY} can be used to + * specify the locale (optional, defaults to {@link Locale#getDefault()}; + * special values DEFAULT and ROOT can be used to set + * {@link Locale#getDefault()} and {@link Locale#ROOT} respectively). + *
  • The property suffix {@link MCRDateTimeStyler#TIME_ZONE_KEY}can be used to + * specify the timezone (optional, defaults to {@link ZoneId#systemDefault()}; + * special value DEFAULT can be used to set {@link ZoneId#systemDefault()}). + *
+ * Example: + *

+ * [...].Class=org.mycore.common.date.MCRDateTimeStyler
+ * [...].Format=yyyy-MM-dd'T'HH:mm
+ * [...].Locale=de_DE
+ * [...].TimeZone=Europe/Berlin
+ * 
+ */ +@MCRConfigurationProxy(proxyClass = MCRDateTimeStyler.Factory.class) +public final class MCRDateTimeStyler extends MCRInstantFormatterBase { + + public static final String DATE_STYLE_KEY = "DateStyle"; + + public static final String TIME_STYLE_KEY = "TimeStyle"; + + public static final String LOCALE_KEY = "Locale"; + + public static final String TIME_ZONE_KEY = "TimeZone"; + + private final DateTimeFormatter formatter; + + public MCRDateTimeStyler(FormatStyle dateStyle, FormatStyle timeStyle) { + this(dateStyle, timeStyle, Locale.getDefault(), ZoneId.systemDefault()); + } + + public MCRDateTimeStyler(FormatStyle dateStyle, FormatStyle timeStyle, Locale locale) { + this(dateStyle, timeStyle, locale, ZoneId.systemDefault()); + } + + @SuppressForbidden + public MCRDateTimeStyler(FormatStyle dateStyle, FormatStyle timeStyle, Locale locale, ZoneId zoneId) { + Objects.requireNonNull(dateStyle, "Date style must not be null"); + Objects.requireNonNull(dateStyle, "Time style must not be null"); + Objects.requireNonNull(locale, "Locale must not be null"); + Objects.requireNonNull(zoneId, "Zone ID must not be null"); + formatter = DateTimeFormatter.ofLocalizedDateTime(dateStyle, timeStyle).localizedBy(locale).withZone(zoneId); + } + + @Override + public String format(Instant instant) { + + return formatter.format(instant); + } + + public static final class Factory implements Supplier { + + @MCRProperty(name = DATE_STYLE_KEY) + public String dateStyle; + + @MCRProperty(name = TIME_STYLE_KEY) + public String timeStyle; + + @MCRProperty(name = LOCALE_KEY, required = false) + public String locale; + + @MCRProperty(name = TIME_ZONE_KEY, required = false) + public String timeZone; + + @Override + public MCRDateTimeStyler get() { + return new MCRDateTimeStyler(FormatStyle.valueOf(dateStyle), FormatStyle.valueOf(timeStyle), + getLocale(locale), getTimeZone(timeZone)); + } + + } + +} diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRFLDateScrambler.java b/mycore-base/src/main/java/org/mycore/common/date/MCRFLDateScrambler.java new file mode 100644 index 0000000000..014efb5cf1 --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/date/MCRFLDateScrambler.java @@ -0,0 +1,60 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.date; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.TimeZone; + +/** + * A {@link MCRFLDateScrambler} is a {@link MCRDateFormatter} that uses a scrambling algorithm introduced by Frank Lützenkirchen to format a date. + *

+ * No configuration options are available. + *

+ * Example: + *


+ * [...].Class=org.mycore.common.date.MCRFLDateScrambler
+ * 
+ */ +public final class MCRFLDateScrambler extends MCRDateFormatterBase { + + @Override + public String format(Date date) { + + Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT+01:00"), Locale.ENGLISH); + calendar.setTime(date); + + int yyy = 2268 - calendar.get(Calendar.YEAR); + int ddd = 500 - calendar.get(Calendar.DAY_OF_YEAR); + int hh = calendar.get(Calendar.HOUR_OF_DAY); + int mm = calendar.get(Calendar.MINUTE); + int ss = calendar.get(Calendar.SECOND); + int sss = 99_999 - (hh * 3600 + mm * 60 + ss); + String ddddd = String.valueOf(yyy * 366 + ddd); + + return String.valueOf(ddddd.charAt(4)) + ddddd.charAt(2) + + ddddd.charAt(1) + ddddd.charAt(3) + ddddd.charAt(0) + sss; + + } + +} diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRISO8601DateFormatter.java b/mycore-base/src/main/java/org/mycore/common/date/MCRISO8601DateFormatter.java new file mode 100644 index 0000000000..8acc940d28 --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/date/MCRISO8601DateFormatter.java @@ -0,0 +1,109 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.date; + +import static org.mycore.common.date.MCRDateFormatterUtils.getLocale; +import static org.mycore.common.date.MCRDateFormatterUtils.getTimeZone; + +import java.time.ZoneId; +import java.util.Date; +import java.util.Locale; +import java.util.Objects; +import java.util.function.Supplier; + +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; +import org.mycore.datamodel.common.MCRISO8601Date; + +/** + * A {@link MCRISO8601DateFormatter} is a {@link MCRDateFormatter} that uses + * {@link MCRISO8601Date#format(String, Locale)} to format a date. + *

+ * The following configuration options are available: + *

    + *
  • The property suffix {@link MCRSimpleDateFormatter#FORMAT_KEY} can be used to + * specify the format. + *
  • The property suffix {@link MCRSimpleDateFormatter#LOCALE_KEY} can be used to + * specify the locale (optional, defaults to {@link Locale#getDefault()}; + * special values DEFAULT and ROOT can be used to set + * {@link Locale#getDefault()} and {@link Locale#ROOT} respectively). + *
  • The property suffix {@link MCRSimpleDateFormatter#TIME_ZONE_KEY}can be used to + * specify the timezone (optional, defaults to {@link ZoneId#systemDefault()}; + * special value DEFAULT can be used to set {@link ZoneId#systemDefault()}). + *
+ * Example: + *

+ * [...].Class=org.mycore.common.date.MCRISO8601DateFormatter
+ * [...].Format=yyyy-MM-dd'T'HH:mm
+ * [...].Locale=de_DE
+ * [...].TimeZone=Europe/Berlin
+ * 
+ */ +@MCRConfigurationProxy(proxyClass = MCRISO8601DateFormatter.Factory.class) +public final class MCRISO8601DateFormatter extends MCRDateFormatterBase { + + private final String format; + + private final Locale locale; + + private final String timeZone; + + public MCRISO8601DateFormatter(String format) { + this(format, Locale.ROOT, ZoneId.systemDefault()); + } + + public MCRISO8601DateFormatter(String format, Locale locale) { + this(format, locale, ZoneId.systemDefault()); + } + + public MCRISO8601DateFormatter(String format, Locale locale, ZoneId zoneId) { + this.format = Objects.requireNonNull(format, "Format must not be null"); + this.locale = Objects.requireNonNull(locale, "Locale must not be null"); + this.timeZone = Objects.requireNonNull(zoneId, "Zone ID must not be null").getId(); + } + + @Override + public String format(Date date) { + + MCRISO8601Date isoDate = new MCRISO8601Date(); + isoDate.setDate(date); + + return isoDate.format(format, locale, timeZone); + + } + + public static final class Factory implements Supplier { + + @MCRProperty(name = "Format") + public String format; + + @MCRProperty(name = "Locale", required = false) + public String locale; + + @MCRProperty(name = "TimeZone", required = false) + public String timeZone; + + @Override + public MCRISO8601DateFormatter get() { + return new MCRISO8601DateFormatter(format, getLocale(locale), getTimeZone(timeZone)); + } + + } + +} diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRInstantFormatterBase.java b/mycore-base/src/main/java/org/mycore/common/date/MCRInstantFormatterBase.java new file mode 100644 index 0000000000..34943d4af1 --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/date/MCRInstantFormatterBase.java @@ -0,0 +1,38 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.date; + +import java.time.Instant; +import java.util.Date; + +/** + * {@link MCRInstantFormatterBase} is a base implementation of {@link MCRDateFormatter} that + * formats {@link Instant} objects. + */ +public non-sealed abstract class MCRInstantFormatterBase implements MCRDateFormatter { + + @Override + public final String format(Date date) { + return format(date.toInstant()); + } + + @Override + public abstract String format(Instant instant); + +} diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRSimpleDateFormatter.java b/mycore-base/src/main/java/org/mycore/common/date/MCRSimpleDateFormatter.java new file mode 100644 index 0000000000..b82861ade2 --- /dev/null +++ b/mycore-base/src/main/java/org/mycore/common/date/MCRSimpleDateFormatter.java @@ -0,0 +1,116 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.common.date; + +import static org.mycore.common.date.MCRDateFormatterUtils.getLocale; +import static org.mycore.common.date.MCRDateFormatterUtils.getTimeZone; + +import java.text.SimpleDateFormat; +import java.time.ZoneId; +import java.util.Date; +import java.util.Locale; +import java.util.Objects; +import java.util.TimeZone; +import java.util.function.Supplier; + +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; + +/** + * A {@link MCRSimpleDateFormatter} is a {@link MCRDateFormatter} that uses + * {@link SimpleDateFormat#format(Date)} to format a date. + *

+ * The following configuration options are available: + *

    + *
  • The property suffix {@link MCRSimpleDateFormatter#FORMAT_KEY} can be used to + * specify the format. + *
  • The property suffix {@link MCRSimpleDateFormatter#LOCALE_KEY} can be used to + * specify the locale (optional, defaults to {@link Locale#getDefault()}; + * special values DEFAULT and ROOT can be used to set + * {@link Locale#getDefault()} and {@link Locale#ROOT} respectively). + *
  • The property suffix {@link MCRSimpleDateFormatter#TIME_ZONE_KEY}can be used to + * specify the timezone (optional, defaults to {@link ZoneId#systemDefault()}; + * special value DEFAULT can be used to set {@link ZoneId#systemDefault()}). + *
+ * Example: + *

+ * [...].Class=org.mycore.common.date.MCRSimpleDateFormatter
+ * [...].Format=yyyy-MM-dd'T'HH:mm
+ * [...].Locale=de_DE
+ * [...].TimeZone=Europe/Berlin
+ * 
+ */ +@MCRConfigurationProxy(proxyClass = MCRSimpleDateFormatter.Factory.class) +public final class MCRSimpleDateFormatter extends MCRDateFormatterBase { + + public static final String FORMAT_KEY = "Format"; + + public static final String LOCALE_KEY = "Locale"; + + public static final String TIME_ZONE_KEY = "TimeZone"; + + private final ThreadLocal formatHolder; + + public MCRSimpleDateFormatter(String format) { + this(format, Locale.getDefault(), ZoneId.systemDefault()); + } + + public MCRSimpleDateFormatter(String format, Locale locale) { + this(format, locale, ZoneId.systemDefault()); + } + + public MCRSimpleDateFormatter(String format, Locale locale, ZoneId zoneId) { + Objects.requireNonNull(format, "Format must not be null"); + Objects.requireNonNull(locale, "Locale must not be null"); + Objects.requireNonNull(zoneId, "Zone ID must not be null"); + formatHolder = ThreadLocal.withInitial(getFormatSupplier(format, locale, TimeZone.getTimeZone(zoneId))); + } + + private static Supplier getFormatSupplier(String format, Locale locale, TimeZone timeZone) { + return () -> { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format, locale); + simpleDateFormat.setTimeZone(timeZone); + return simpleDateFormat; + }; + } + + @Override + public String format(Date date) { + return formatHolder.get().format(date); + } + + public static final class Factory implements Supplier { + + @MCRProperty(name = FORMAT_KEY) + public String format; + + @MCRProperty(name = LOCALE_KEY, required = false) + public String locale; + + @MCRProperty(name = TIME_ZONE_KEY, required = false) + public String timeZone; + + @Override + public MCRSimpleDateFormatter get() { + return new MCRSimpleDateFormatter(format, getLocale(locale), getTimeZone(timeZone)); + } + + } + +} diff --git a/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRDefaultXMappingClassificationGenerator.java b/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRDefaultXMappingClassificationGenerator.java index e48e9f95e4..daf5c02dae 100644 --- a/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRDefaultXMappingClassificationGenerator.java +++ b/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRDefaultXMappingClassificationGenerator.java @@ -59,9 +59,9 @@ *
  • The property suffix {@link MCRXMappingClassificationGeneratorBase#EVALUATOR_KEY} can be used to * specify the evaluator used to obtain category IDs from. *
  • For the evaluator, the property suffix {@link MCRSentinel#DEFAULT_KEY} can be used to - * exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead. + * exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead. *
  • The property suffix {@link MCRXMappingClassificationGeneratorBase#ON_MISSING_MAPPED_CATEGORY_KEY} can be used to - * specify the behaviour, when a mapped category ID is missing. + * specify the behavior, when a mapped category ID is missing. * * Example: *
    
    diff --git a/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRXPathClassificationMappingCondition.java b/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRXPathClassificationMappingCondition.java
    index 3133a3127f..7942610f4d 100644
    --- a/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRXPathClassificationMappingCondition.java
    +++ b/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRXPathClassificationMappingCondition.java
    @@ -31,13 +31,13 @@
     
     /**
      * A {@link MCRXPathClassificationMappingCondition} is a {@link Condition} that
    - * determines the condition of a MyCoRe object based on a configurable X-Path evaluating the
    + * determines the condition of a MyCoRe object based on a configurable XPath evaluating the
      * XML representation of the MyCoRe object.
      * 

    * The following configuration options are available: *

      *
    • The property suffix {@link MCRXPathClassificationMappingCondition#X_PATH_KEY} can be used to - * specify the X-Path to be used. + * specify the XPath to be used. *
    * Example: *
    
    @@ -53,7 +53,7 @@ public final class MCRXPathClassificationMappingCondition implements Condition {
         private final String xPath;
     
         public MCRXPathClassificationMappingCondition(String xPath) {
    -        this.xPath = Objects.requireNonNull(xPath, "X-Path must not be null");
    +        this.xPath = Objects.requireNonNull(xPath, "XPath must not be null");
         }
     
         @Override
    diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRDateStylerTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRDateStylerTest.java
    new file mode 100644
    index 0000000000..815c582fc3
    --- /dev/null
    +++ b/mycore-base/src/test/java/org/mycore/common/date/MCRDateStylerTest.java
    @@ -0,0 +1,98 @@
    +/*
    + * This file is part of ***  M y C o R e  ***
    + * See https://www.mycore.de/ for details.
    + *
    + * MyCoRe is free software: you can redistribute it and/or modify
    + * it under the terms of the GNU General Public License as published by
    + * the Free Software Foundation, either version 3 of the License, or
    + * (at your option) any later version.
    + *
    + * MyCoRe is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    + * GNU General Public License for more details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with MyCoRe.  If not, see .
    + */
    +
    +package org.mycore.common.date;
    +
    +import static java.time.format.FormatStyle.LONG;
    +import static java.time.format.FormatStyle.SHORT;
    +import static org.junit.jupiter.api.Assertions.assertEquals;
    +
    +import java.time.Instant;
    +import java.time.LocalDateTime;
    +import java.time.ZoneId;
    +import java.time.format.FormatStyle;
    +import java.util.ArrayList;
    +import java.util.Date;
    +import java.util.List;
    +import java.util.Locale;
    +import java.util.stream.Stream;
    +
    +import org.junit.jupiter.params.ParameterizedTest;
    +import org.junit.jupiter.params.provider.Arguments;
    +import org.junit.jupiter.params.provider.MethodSource;
    +
    +public class MCRDateStylerTest {
    +
    +    public static final ZoneId ZONE_ID = ZoneId.systemDefault();
    +
    +    private static Stream provideDates() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), SHORT, "01.01.00"));
    +        argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), LONG, "1. Januar 2000"));
    +        argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), SHORT, "01.12.00"));
    +        argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), LONG, "1. Dezember 2000"));
    +        argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), SHORT, "10.11.12"));
    +        argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), LONG, "10. November 2012"));
    +        argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), SHORT, "05.11.25"));
    +        argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), LONG, "5. November 2025"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Date toDate(int year, int month, int day, int hour, int minute) {
    +        return Date.from(toInstant(year, month, day, hour, minute));
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideDates")
    +    public void formatDate(Date date, FormatStyle dateFormat, String expected) {
    +
    +        String formattedDate = new MCRDateStyler(dateFormat, Locale.GERMAN).format(date);
    +
    +        assertEquals(expected, formattedDate);
    +
    +    }
    +
    +    private static Stream provideInstants() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), SHORT, "01.01.00"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), LONG, "1. Januar 2000"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), SHORT, "01.12.00"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), LONG, "1. Dezember 2000"));
    +        argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), SHORT, "10.11.12"));
    +        argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), LONG, "10. November 2012"));
    +        argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), SHORT, "05.11.25"));
    +        argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), LONG, "5. November 2025"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Instant toInstant(int year, int month, int day, int hour, int minute) {
    +        LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
    +        return localDateTime.atZone(ZONE_ID).toInstant();
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideInstants")
    +    public void formatInstant(Instant instant, FormatStyle dateFormat, String expected) {
    +
    +        String formattedDate = new MCRDateStyler(dateFormat, Locale.GERMAN).format(instant);
    +
    +        assertEquals(expected, formattedDate);
    +
    +    }
    +
    +}
    diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeFormatterTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeFormatterTest.java
    new file mode 100644
    index 0000000000..bcb6f6e0a8
    --- /dev/null
    +++ b/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeFormatterTest.java
    @@ -0,0 +1,98 @@
    +/*
    + * This file is part of ***  M y C o R e  ***
    + * See https://www.mycore.de/ for details.
    + *
    + * MyCoRe is free software: you can redistribute it and/or modify
    + * it under the terms of the GNU General Public License as published by
    + * the Free Software Foundation, either version 3 of the License, or
    + * (at your option) any later version.
    + *
    + * MyCoRe is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    + * GNU General Public License for more details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with MyCoRe.  If not, see .
    + */
    +
    +package org.mycore.common.date;
    +
    +import static org.junit.jupiter.api.Assertions.assertEquals;
    +
    +import java.time.Instant;
    +import java.time.LocalDateTime;
    +import java.time.ZoneId;
    +import java.util.ArrayList;
    +import java.util.Date;
    +import java.util.List;
    +import java.util.stream.Stream;
    +
    +import org.junit.jupiter.params.ParameterizedTest;
    +import org.junit.jupiter.params.provider.Arguments;
    +import org.junit.jupiter.params.provider.MethodSource;
    +
    +public class MCRDateTimeFormatterTest {
    +
    +    public static final String DATE_FORMAT_1 = "yyyyMMddHHmmss";
    +
    +    public static final String DATE_FORMAT_2 = "yyyy-MM-dd'T'HH:mm";
    +
    +    public static final ZoneId ZONE_ID = ZoneId.systemDefault();
    +
    +    private static Stream provideDates() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
    +        argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
    +        argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
    +        argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
    +        argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
    +        argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
    +        argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
    +        argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Date toDate(int year, int month, int day, int hour, int minute) {
    +        return Date.from(toInstant(year, month, day, hour, minute));
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideDates")
    +    public void formatDate(Date date, String format, String expected) {
    +
    +        String formattedDate = new MCRDateTimeFormatter(format).format(date);
    +
    +        assertEquals(expected, formattedDate);
    +
    +    }
    +
    +    private static Stream provideInstants() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
    +        argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
    +        argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
    +        argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
    +        argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Instant toInstant(int year, int month, int day, int hour, int minute) {
    +        LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
    +        return localDateTime.atZone(ZONE_ID).toInstant();
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideInstants")
    +    public void formatInstant(Instant instant, String format, String expected) {
    +
    +        String formattedDate = new MCRDateTimeFormatter(format).format(instant);
    +
    +        assertEquals(expected, formattedDate);
    +
    +    }
    +
    +}
    diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeStylerTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeStylerTest.java
    new file mode 100644
    index 0000000000..31d937be04
    --- /dev/null
    +++ b/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeStylerTest.java
    @@ -0,0 +1,99 @@
    +/*
    + * This file is part of ***  M y C o R e  ***
    + * See https://www.mycore.de/ for details.
    + *
    + * MyCoRe is free software: you can redistribute it and/or modify
    + * it under the terms of the GNU General Public License as published by
    + * the Free Software Foundation, either version 3 of the License, or
    + * (at your option) any later version.
    + *
    + * MyCoRe is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    + * GNU General Public License for more details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with MyCoRe.  If not, see .
    + */
    +
    +package org.mycore.common.date;
    +
    +import static java.time.format.FormatStyle.LONG;
    +import static java.time.format.FormatStyle.MEDIUM;
    +import static java.time.format.FormatStyle.SHORT;
    +import static org.junit.jupiter.api.Assertions.assertEquals;
    +
    +import java.time.Instant;
    +import java.time.LocalDateTime;
    +import java.time.ZoneId;
    +import java.time.format.FormatStyle;
    +import java.util.ArrayList;
    +import java.util.Date;
    +import java.util.List;
    +import java.util.Locale;
    +import java.util.stream.Stream;
    +
    +import org.junit.jupiter.params.ParameterizedTest;
    +import org.junit.jupiter.params.provider.Arguments;
    +import org.junit.jupiter.params.provider.MethodSource;
    +
    +public class MCRDateTimeStylerTest {
    +
    +    public static final ZoneId ZONE_ID = ZoneId.systemDefault();
    +
    +    private static Stream provideDates() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), SHORT, SHORT, "01.01.00, 00:00"));
    +        argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), LONG, MEDIUM, "1. Januar 2000, 00:00:00"));
    +        argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), SHORT, SHORT, "01.12.00, 10:12"));
    +        argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), LONG, MEDIUM, "1. Dezember 2000, 10:12:00"));
    +        argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), SHORT, SHORT, "10.11.12, 09:08"));
    +        argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), LONG, MEDIUM, "10. November 2012, 09:08:00"));
    +        argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), SHORT, SHORT, "05.11.25, 13:00"));
    +        argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), LONG, MEDIUM, "5. November 2025, 13:00:00"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Date toDate(int year, int month, int day, int hour, int minute) {
    +        return Date.from(toInstant(year, month, day, hour, minute));
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideDates")
    +    public void formatDate(Date date, FormatStyle dateFormat, FormatStyle timeFormat, String expected) {
    +
    +        String formattedDate = new MCRDateTimeStyler(dateFormat, timeFormat, Locale.GERMAN).format(date);
    +
    +        assertEquals(expected, formattedDate);
    +
    +    }
    +
    +    private static Stream provideInstants() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), SHORT, SHORT, "01.01.00, 00:00"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), LONG, MEDIUM, "1. Januar 2000, 00:00:00"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), SHORT, SHORT, "01.12.00, 10:12"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), LONG, MEDIUM, "1. Dezember 2000, 10:12:00"));
    +        argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), SHORT, SHORT, "10.11.12, 09:08"));
    +        argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), LONG, MEDIUM, "10. November 2012, 09:08:00"));
    +        argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), SHORT, SHORT, "05.11.25, 13:00"));
    +        argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), LONG, MEDIUM, "5. November 2025, 13:00:00"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Instant toInstant(int year, int month, int day, int hour, int minute) {
    +        LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
    +        return localDateTime.atZone(ZONE_ID).toInstant();
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideInstants")
    +    public void formatInstant(Instant instant, FormatStyle dateFormat, FormatStyle timeFormat, String expected) {
    +
    +        String formattedDate = new MCRDateTimeStyler(dateFormat, timeFormat, Locale.GERMAN).format(instant);
    +
    +        assertEquals(expected, formattedDate);
    +
    +    }
    +
    +}
    diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRFLDateScamblerTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRFLDateScamblerTest.java
    new file mode 100644
    index 0000000000..ac42856f27
    --- /dev/null
    +++ b/mycore-base/src/test/java/org/mycore/common/date/MCRFLDateScamblerTest.java
    @@ -0,0 +1,159 @@
    +/*
    + * This file is part of ***  M y C o R e  ***
    + * See https://www.mycore.de/ for details.
    + *
    + * MyCoRe is free software: you can redistribute it and/or modify
    + * it under the terms of the GNU General Public License as published by
    + * the Free Software Foundation, either version 3 of the License, or
    + * (at your option) any later version.
    + *
    + * MyCoRe is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    + * GNU General Public License for more details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with MyCoRe.  If not, see .
    + */
    +
    +package org.mycore.common.date;
    +
    +import static org.junit.jupiter.api.Assertions.assertEquals;
    +
    +import java.time.Instant;
    +import java.time.LocalDate;
    +import java.time.LocalDateTime;
    +import java.time.ZoneId;
    +import java.time.format.DateTimeFormatter;
    +import java.util.ArrayList;
    +import java.util.Date;
    +import java.util.List;
    +import java.util.TimeZone;
    +import java.util.stream.Stream;
    +
    +import org.apache.logging.log4j.LogManager;
    +import org.apache.logging.log4j.Logger;
    +import org.junit.jupiter.params.ParameterizedTest;
    +import org.junit.jupiter.params.provider.Arguments;
    +import org.junit.jupiter.params.provider.MethodSource;
    +
    +public class MCRFLDateScamblerTest {
    +
    +    private static final Logger LOGGER = LogManager.getLogger();
    +
    +    public static final ZoneId ZONE_ID = TimeZone.getTimeZone("GMT+01:00").toZoneId();
    +
    +    private static Stream provideDates() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), "7588999999"));
    +        argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), "2285963279"));
    +        argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), "1838967119"));
    +        argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), "9192853199"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Date toDate(int year, int month, int day, int hour, int minute) {
    +        return Date.from(toInstant(year, month, day, hour, minute));
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideDates")
    +    public void formatDate(Date date, String expectedScramble) {
    +
    +        String scambled = new MCRFLDateScrambler().format(date);
    +        String unscrambled = unscramble(scambled);
    +
    +        LOGGER.info("Date: {} => {} => {}", date, scambled, unscrambled);
    +
    +        assertEquals(expectedScramble, scambled);
    +
    +    }
    +
    +    private static Stream provideInstants() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), "7588999999"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), "2285963279"));
    +        argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), "1838967119"));
    +        argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), "9192853199"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Instant toInstant(int year, int month, int day, int hour, int minute) {
    +        LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
    +        return localDateTime.atZone(ZONE_ID).toInstant();
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideInstants")
    +    public void formatInstant(Instant instant, String expectedScramble) {
    +
    +        String scambled = new MCRFLDateScrambler().format(instant);
    +        String unscrambled = unscramble(scambled);
    +
    +        LOGGER.info("Instant: {} => {} => {}", instant, scambled, unscrambled);
    +
    +        assertEquals(expectedScramble, scambled);
    +
    +    }
    +
    +    @SuppressWarnings("unused")
    +    public static String unscramble(String scramble) {
    +
    +        if (!scramble.matches("[0-9]{10}")) {
    +            return "Invalid string length";
    +        }
    +
    +        String prefix = scramble.substring(0, 5);
    +        String sssString = scramble.substring(5);
    +
    +        char[] dddddChars = new char[5];
    +        dddddChars[4] = prefix.charAt(0);
    +        dddddChars[2] = prefix.charAt(1);
    +        dddddChars[1] = prefix.charAt(2);
    +        dddddChars[3] = prefix.charAt(3);
    +        dddddChars[0] = prefix.charAt(4);
    +        String dddddString = new String(dddddChars);
    +
    +        String decodedDate = "YYYY-MM-DD";
    +        String decodedTime = "HH:mm:ss";
    +
    +        try {
    +
    +            int ddddd = Integer.parseInt(dddddString);
    +
    +            int yyy = (ddddd - 134) / 366;
    +            int ddd = ddddd - (yyy * 366);
    +
    +            int year = 2268 - yyy;
    +            int dayOfYear = 500 - ddd;
    +
    +            LocalDate date = LocalDate.ofYearDay(year, dayOfYear);
    +            decodedDate = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
    +
    +        } catch (Exception e) {
    +            decodedDate = "INVALID_DATE";
    +        }
    +
    +        try {
    +
    +            int sss = Integer.parseInt(sssString);
    +            int totalSeconds = 99999 - sss;
    +
    +            int hh = totalSeconds / 3600;
    +            int mm = (totalSeconds % 3600) / 60;
    +            int ss = totalSeconds % 60;
    +
    +            if (hh >= 0 && hh < 24 && mm >= 0 && mm < 60 && ss >= 0 && ss < 60) {
    +                decodedTime = String.format("%02d:%02d:%02d", hh, mm, ss);
    +            } else {
    +                decodedTime = "INVALID_TIME";
    +            }
    +
    +        } catch (NumberFormatException e) {
    +            decodedTime = "INVALID_TIME";
    +        }
    +
    +        return decodedDate + " " + decodedTime;
    +    }
    +
    +}
    diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRISO8601DateFormatterTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRISO8601DateFormatterTest.java
    new file mode 100644
    index 0000000000..fd0d611d51
    --- /dev/null
    +++ b/mycore-base/src/test/java/org/mycore/common/date/MCRISO8601DateFormatterTest.java
    @@ -0,0 +1,98 @@
    +/*
    + * This file is part of ***  M y C o R e  ***
    + * See https://www.mycore.de/ for details.
    + *
    + * MyCoRe is free software: you can redistribute it and/or modify
    + * it under the terms of the GNU General Public License as published by
    + * the Free Software Foundation, either version 3 of the License, or
    + * (at your option) any later version.
    + *
    + * MyCoRe is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    + * GNU General Public License for more details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with MyCoRe.  If not, see .
    + */
    +
    +package org.mycore.common.date;
    +
    +import static org.junit.jupiter.api.Assertions.assertEquals;
    +
    +import java.time.Instant;
    +import java.time.LocalDateTime;
    +import java.time.ZoneId;
    +import java.util.ArrayList;
    +import java.util.Date;
    +import java.util.List;
    +import java.util.stream.Stream;
    +
    +import org.junit.jupiter.params.ParameterizedTest;
    +import org.junit.jupiter.params.provider.Arguments;
    +import org.junit.jupiter.params.provider.MethodSource;
    +
    +public class MCRISO8601DateFormatterTest {
    +
    +    public static final String DATE_FORMAT_1 = "yyyyMMddHHmmss";
    +
    +    public static final String DATE_FORMAT_2 = "yyyy-MM-dd'T'HH:mm";
    +
    +    public static final ZoneId ZONE_ID = ZoneId.systemDefault();
    +
    +    private static Stream provideDates() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
    +        argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
    +        argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
    +        argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
    +        argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
    +        argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
    +        argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
    +        argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Date toDate(int year, int month, int day, int hour, int minute) {
    +        return Date.from(toInstant(year, month, day, hour, minute));
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideDates")
    +    public void formatDate(Date date, String format, String expected) {
    +
    +        String formattedDate = new MCRISO8601DateFormatter(format).format(date);
    +
    +        assertEquals(expected, formattedDate);
    +
    +    }
    +
    +    private static Stream provideInstants() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
    +        argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
    +        argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
    +        argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
    +        argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Instant toInstant(int year, int month, int day, int hour, int minute) {
    +        LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
    +        return localDateTime.atZone(ZONE_ID).toInstant();
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideInstants")
    +    public void formatInstant(Instant instant, String format, String expected) {
    +
    +        String formattedDate = new MCRISO8601DateFormatter(format).format(instant);
    +
    +        assertEquals(expected, formattedDate);
    +
    +    }
    +
    +}
    diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRMockDateFormatter.java b/mycore-base/src/test/java/org/mycore/common/date/MCRMockDateFormatter.java
    new file mode 100644
    index 0000000000..e410e8d725
    --- /dev/null
    +++ b/mycore-base/src/test/java/org/mycore/common/date/MCRMockDateFormatter.java
    @@ -0,0 +1,20 @@
    +package org.mycore.common.date;
    +
    +import java.time.Instant;
    +
    +public class MCRMockDateFormatter extends MCRInstantFormatterBase {
    +
    +    private final ThreadLocal lastFormattedDateHolder = new ThreadLocal<>();
    +
    +    @Override
    +    public String format(Instant instant) {
    +        String formatted = Long.toString(instant.getEpochSecond());
    +        lastFormattedDateHolder.set(formatted);
    +        return formatted;
    +    }
    +
    +    public String lastFormattedDate() {
    +        return lastFormattedDateHolder.get();
    +    }
    +
    +}
    diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRSimpleDateFormatterTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRSimpleDateFormatterTest.java
    new file mode 100644
    index 0000000000..8f51753bcb
    --- /dev/null
    +++ b/mycore-base/src/test/java/org/mycore/common/date/MCRSimpleDateFormatterTest.java
    @@ -0,0 +1,98 @@
    +/*
    + * This file is part of ***  M y C o R e  ***
    + * See https://www.mycore.de/ for details.
    + *
    + * MyCoRe is free software: you can redistribute it and/or modify
    + * it under the terms of the GNU General Public License as published by
    + * the Free Software Foundation, either version 3 of the License, or
    + * (at your option) any later version.
    + *
    + * MyCoRe is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    + * GNU General Public License for more details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with MyCoRe.  If not, see .
    + */
    +
    +package org.mycore.common.date;
    +
    +import static org.junit.jupiter.api.Assertions.assertEquals;
    +
    +import java.time.Instant;
    +import java.time.LocalDateTime;
    +import java.time.ZoneId;
    +import java.util.ArrayList;
    +import java.util.Date;
    +import java.util.List;
    +import java.util.stream.Stream;
    +
    +import org.junit.jupiter.params.ParameterizedTest;
    +import org.junit.jupiter.params.provider.Arguments;
    +import org.junit.jupiter.params.provider.MethodSource;
    +
    +public class MCRSimpleDateFormatterTest {
    +
    +    public static final String DATE_FORMAT_1 = "yyyyMMddHHmmss";
    +
    +    public static final String DATE_FORMAT_2 = "yyyy-MM-dd'T'HH:mm";
    +
    +    public static final ZoneId ZONE_ID = ZoneId.systemDefault();
    +
    +    private static Stream provideDates() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
    +        argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
    +        argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
    +        argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
    +        argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
    +        argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
    +        argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
    +        argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Date toDate(int year, int month, int day, int hour, int minute) {
    +        return Date.from(toInstant(year, month, day, hour, minute));
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideDates")
    +    public void formatDate(Date date, String format, String expected) {
    +
    +        String formattedDate = new MCRSimpleDateFormatter(format).format(date);
    +
    +        assertEquals(expected, formattedDate);
    +
    +    }
    +
    +    private static Stream provideInstants() {
    +        List argumentsList = new ArrayList<>();
    +        argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
    +        argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
    +        argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
    +        argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
    +        argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
    +        argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
    +        return argumentsList.stream();
    +    }
    +
    +    private static Instant toInstant(int year, int month, int day, int hour, int minute) {
    +        LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
    +        return localDateTime.atZone(ZONE_ID).toInstant();
    +    }
    +
    +    @ParameterizedTest
    +    @MethodSource("provideInstants")
    +    public void formatInstant(Instant instant, String format, String expected) {
    +
    +        String formattedDate = new MCRSimpleDateFormatter(format).format(instant);
    +
    +        assertEquals(expected, formattedDate);
    +
    +    }
    +
    +}
    diff --git a/mycore-mods/src/main/java/org/mycore/mods/classification/mapping/MCRMODSXMappingClassificationGenerator.java b/mycore-mods/src/main/java/org/mycore/mods/classification/mapping/MCRMODSXMappingClassificationGenerator.java
    index b07fb28850..cc9870f5c3 100644
    --- a/mycore-mods/src/main/java/org/mycore/mods/classification/mapping/MCRMODSXMappingClassificationGenerator.java
    +++ b/mycore-mods/src/main/java/org/mycore/mods/classification/mapping/MCRMODSXMappingClassificationGenerator.java
    @@ -57,9 +57,9 @@
      * 
  • The property suffix {@link MCRXMappingClassificationGeneratorBase#EVALUATOR_KEY} can be used to * specify the evaluator used to obtain category IDs from. *
  • For the evaluator, the property suffix {@link MCRSentinel#DEFAULT_KEY} can be used to - * exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead. + * exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead. *
  • The property suffix {@link MCRXMappingClassificationGeneratorBase#ON_MISSING_MAPPED_CATEGORY_KEY} can be used to - * specify the behaviour, when a mapped classification value is missing. + * specify the behavior, when a mapped classification value is missing. * * Example: *
    
    diff --git a/mycore-pi/src/main/java/org/mycore/pi/MCRGenericPIGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/MCRGenericPIGenerator.java
    index e1a833eb4b..373874bc35 100644
    --- a/mycore-pi/src/main/java/org/mycore/pi/MCRGenericPIGenerator.java
    +++ b/mycore-pi/src/main/java/org/mycore/pi/MCRGenericPIGenerator.java
    @@ -18,22 +18,19 @@
     
     package org.mycore.pi;
     
    +import static org.mycore.pi.util.MCRPIGeneratorUtils.getCountPattern;
    +import static org.mycore.pi.util.MCRPIGeneratorUtils.readCountFromDatabase;
    +
     import java.text.DecimalFormat;
     import java.text.DecimalFormatSymbols;
    -import java.text.SimpleDateFormat;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.Comparator;
     import java.util.Date;
     import java.util.HashMap;
     import java.util.List;
     import java.util.Locale;
     import java.util.Map;
     import java.util.Objects;
    -import java.util.Optional;
     import java.util.concurrent.atomic.AtomicInteger;
    -import java.util.function.Predicate;
    -import java.util.regex.Matcher;
    +import java.util.function.Supplier;
     import java.util.regex.Pattern;
     import java.util.stream.Collectors;
     import java.util.stream.IntStream;
    @@ -49,223 +46,238 @@
     import org.mycore.common.MCRConstants;
     import org.mycore.common.MCRException;
     import org.mycore.common.config.MCRConfigurationException;
    -import org.mycore.common.config.annotation.MCRPostConstruction;
    +import org.mycore.common.config.annotation.MCRConfigurationProxy;
    +import org.mycore.common.config.annotation.MCRInstance;
    +import org.mycore.common.config.annotation.MCRProperty;
    +import org.mycore.common.config.annotation.MCRPropertyList;
    +import org.mycore.common.config.annotation.MCRPropertyMap;
    +import org.mycore.common.date.MCRDateFormatter;
    +import org.mycore.common.date.MCRSimpleDateFormatter;
     import org.mycore.datamodel.metadata.MCRBase;
     import org.mycore.datamodel.metadata.MCRObjectID;
     import org.mycore.datamodel.metadata.MCRObjectService;
     import org.mycore.pi.exceptions.MCRPersistentIdentifierException;
    +import org.mycore.pi.urn.MCRDNBURN;
     
     /**
    - *
    - * MCR.PI.Generator.myGenerator.Class=org.mycore.pi.urn.MCRGenericPIGenerator
    - * 

    - * Set a generic pattern. - *

    - * MCR.PI.Generator.myGenerator.GeneralPattern=urn:nbn:de:gbv:$CurrentDate-$1-$2-$ObjectType-$ObjectProject-$ObjectNumber-$Count- - * MCR.PI.Generator.myGenerator.GeneralPattern=urn:nbn:de:gbv:$ObjectDate-$ObjectType-$Count - * MCR.PI.Generator.myGenerator.GeneralPattern=urn:nbn:de:gbv:$ObjectDate-$Count - * MCR.PI.Generator.myGenerator.GeneralPattern=urn:nbn:de:gbv:$ObjectType-$Count - * MCR.PI.Generator.myGenerator.GeneralPattern=urn:nbn:de:gbv:$0-$1-$Count - *

    - * Set an optional DateFormat, if not set the ddMMyyyy is just used as value. (SimpleDateFormat) - *

    - * MCR.PI.Generator.myGenerator.DateFormat=ddMMyyyy - *

    - * Set an optional ObjectType mapping, if not set the ObjectType is just used as value - *

    - * MCR.PI.Generator.myGenerator.TypeMapping=document:doc,disshab:diss,Thesis:Thesis,bundle:doc,mods:test - *

    - * You can also map the projectid - *

    - * Set an optional Count precision, if not set or set to -1 the pure number is used (1,2,.., 999). - * Count always relativ to type and date. - *

    - * MCR.PI.Generator.myGenerator.CountPrecision=3 # will produce 001, 002, ... , 999 + * {@link MCRGenericPIGenerator} is a {@link MCRPIGenerator} for arbitrary identifiers + * that generates identifiers using a general pattern and other information dependent on the + * replacement markers contained in that pattern. + *

      + *
    • + * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_CURRENT_DATE} + * will be replaced with the current date, formatted by the given date formatter. + *
    • + *
    • + * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_OBJECT_DATE} + * will be replaced with the objects creation date, formatted by the given date formatter. + *
    • + *
    • + * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_OBJECT_PROJECT} + * will be replaced with the object IDs {@link MCRObjectID#getProjectId()}, mapped by the given project mapping. + *
    • + *
    • + * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_OBJECT_TYPE} + * will be replaced with the object IDs {@link MCRObjectID#getTypeId()}, mapped by the given type mapping. + *
    • + *
    • + * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_OBJECT_NUMBER} + * will be replaced with the object IDs {@link MCRObjectID#getNumberAsString()}. + *
    • + *
    • + * The replacement marker of form {@link MCRGenericPIGenerator#XPATH_PATTERN} + * will be replaced with the result of evaluating the objects XML representation with + * a given xPath, using the n-th given xPath, when the pattern represents the number n. + *
    • + *
    • + * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_COUNT} + * will be replaced with a unique count number with the given count precision. + *
    • + *
    *

    - * Set the Type of the generated pi. + * Example patterns: + *

    
    + * urn:nbn:de:gbv:$CurrentDate-$1-$2-$ObjectType-$ObjectProject-$ObjectNumber-$Count-
    + * urn:nbn:de:gbv:$ObjectDate-$ObjectType-$Count
    + * urn:nbn:de:gbv:$ObjectDate-$Count
    + * urn:nbn:de:gbv:$ObjectType-$Count
    + * urn:nbn:de:gbv:$0-$1-$Count
    + * 
    *

    - * MCR.PI.Generator.myGenerator.Type=dnbURN - *

    - * - * Set the Xpaths - *

    - * MCR.PI.Generator.myGenerator.XPath.1=/mycoreobject/metadata/def.shelf/shelf/ - * MCR.PI.Generator.myGenerator.XPath.2=/mycoreobject/metadata/def.path2/path2/ - * - * @author Sebastian Hofmann + * The following configuration options are available: + *

      + *
    • The property suffix {@link MCRGenericPIGenerator#GENERAL_PATTERN_KEY} can be used to + * specify the pattern. + *
    • The property suffix {@link MCRGenericPIGenerator#DATE_FORMATTER_KEY} can be used to + * specify the date formatter to be used (optional, defaults to {@link MCRSimpleDateFormatter} with format + * {@link MCRGenericPIGenerator#DEFAULT_DATE_FORMAT} and locale {@link MCRGenericPIGenerator#DEFAULT_DATE_LOCALE}). + *
    • The property suffix {@link MCRGenericPIGenerator#OBJECT_PROJECT_MAPPING_KEY} can be used to + * specify the project ID mappings to be used. + *
    • The property suffix {@link MCRGenericPIGenerator#OBJECT_TYPE_MAPPING_KEY} can be used to + * specify the type ID mapping to be used. + *
    • The property suffix {@link MCRGenericPIGenerator#COUNT_PRECISION_KEY} can be used to + * specify number of digits to be used for the count (optional, defaults to -1, + * which uses the natural number of digits). + *
    • The property suffix {@link MCRGenericPIGenerator#TYPE_KEY} can be used to + * specify identifier type. + *
    • The property suffix {@link MCRGenericPIGenerator#X_PATH_KEY} can be used to + * specify the list of xPaths. + *
    + * Example: + *
    
    + * [...].Class=org.mycore.org.mycore.pi.MCRGenericPIGenerator
    + * [...].GeneralPattern=urn:nbn:de:gbv:$CurrentDate-$1-$2-$ObjectType-$ObjectProject-$ObjectNumber-$Count-
    + * [...].DateFormatter.Class=org.mycore.common.date.MCRSimpleDateFormatter
    + * [...].DateFormatter.Format=yyyy-MM-dd
    + * [...].ObjectProjectMapping.mycore=MyCoRe
    + * [...].ObjectTypeMapping.mods=MODS
    + * [...].CountPrecision=6
    + * [...].Type=dnbUrn
    + * [...].XPath.1=/mycoreobject/metadata//mods:typeOfResource/text()
    + * [...].XPath.2=substring-after(/mycoreobject/metadata//mods:genre/@valueURI,'#')
    + * 
    */ -public class MCRGenericPIGenerator extends MCRPIGenerator { - - static final String PLACE_HOLDER_CURRENT_DATE = "$CurrentDate"; +@MCRConfigurationProxy(proxyClass = MCRGenericPIGenerator.Factory.class) +public class MCRGenericPIGenerator implements MCRPIGenerator { - static final String PLACE_HOLDER_OBJECT_DATE = "$ObjectDate"; + public static final String DEFAULT_DATE_FORMAT = "ddMMyyyy"; - static final String PLACE_HOLDER_OBJECT_TYPE = "$ObjectType"; + public static final Locale DEFAULT_DATE_LOCALE = Locale.ROOT; - static final String PLACE_HOLDER_OBJECT_PROJECT = "$ObjectProject"; + public static final String GENERAL_PATTERN_KEY = "GeneralPattern"; - static final String PLACE_HOLDER_COUNT = "$Count"; + public static final String DATE_FORMATTER_KEY = "DateFormatter"; - static final String PLACE_HOLDER_OBJECT_NUMBER = "$ObjectNumber"; + public static final String OBJECT_PROJECT_MAPPING_KEY = "ObjectProjectMapping"; - private static final Logger LOGGER = LogManager.getLogger(); + public static final String OBJECT_TYPE_MAPPING_KEY = "ObjectTypeMapping"; - private static final String PROPERTY_KEY_GENERAL_PATTERN = "GeneralPattern"; + public static final String COUNT_PRECISION_KEY = "CountPrecision"; - private static final String PROPERTY_KEY_DATE_FORMAT = "DateFormat"; + public static final String TYPE_KEY = "Type"; - private static final String PROPERTY_KEY_OBJECT_TYPE_MAPPING = "ObjectTypeMapping"; + public static final String X_PATH_KEY = "XPath"; - private static final String PROPERTY_KEY_OBJECT_PROJECT_MAPPING = "ObjectProjectMapping"; + public static final String PLACE_HOLDER_CURRENT_DATE = "$CurrentDate"; - private static final String PROPERTY_KEY_COUNT_PRECISION = "CountPrecision"; + public static final String PLACE_HOLDER_OBJECT_DATE = "$ObjectDate"; - private static final String PROPERTY_KEY_XPATH = "XPath"; - - private static final String PROPERTY_KEY_TYPE = "Type"; - - private static final Map PATTERN_COUNT_MAP = new HashMap<>(); - - private static final Pattern XPATH_PATTERN = Pattern.compile("\\$([0-9]+)", Pattern.DOTALL); + public static final String PLACE_HOLDER_OBJECT_PROJECT = "$ObjectProject"; - private String generalPattern; + public static final String PLACE_HOLDER_OBJECT_TYPE = "$ObjectType"; - private SimpleDateFormat dateFormat; + public static final String PLACE_HOLDER_COUNT = "$Count"; - private String objectTypeMapping; + public static final String PLACE_HOLDER_OBJECT_NUMBER = "$ObjectNumber"; - private String objectProjectMapping; - - private int countPrecision; - - private String type; - - private String[] xpath; + private static final Logger LOGGER = LogManager.getLogger(); - public MCRGenericPIGenerator() { - super(); - } + private static final Map PATTERN_COUNT_MAP = new HashMap<>(); - @Override - @MCRPostConstruction - public void init(String property) { - super.init(property); - final Map properties = getProperties(); + private static final Pattern XPATH_PATTERN = Pattern.compile("\\$([0-9]+)", Pattern.DOTALL); - setGeneralPattern(properties.get(PROPERTY_KEY_GENERAL_PATTERN)); + private final String generalPattern; - setDateFormat(Optional.ofNullable(properties.get(PROPERTY_KEY_DATE_FORMAT)) - .map(format -> new SimpleDateFormat(format, Locale.ROOT)) - .orElse(new SimpleDateFormat("ddMMyyyy", Locale.ROOT))); + private final MCRDateFormatter dateFormatter; - setObjectTypeMapping(properties.get(PROPERTY_KEY_OBJECT_TYPE_MAPPING)); - setObjectProjectMapping(properties.get(PROPERTY_KEY_OBJECT_PROJECT_MAPPING)); + private final Map projectIdMappings; - setCountPrecision(Optional.ofNullable(properties.get(PROPERTY_KEY_COUNT_PRECISION)) - .map(Integer::parseInt) - .orElse(-1)); + private final Map typeIdMappings; - setType(properties.get(PROPERTY_KEY_TYPE)); + private final int countPrecision; - List xpaths = new ArrayList<>(); - int count = 1; - while (properties.containsKey(PROPERTY_KEY_XPATH + "." + count)) { - xpaths.add(properties.get(PROPERTY_KEY_XPATH + "." + count)); - count++; - } + private final String type; - setXpath(xpaths.toArray(new String[0])); - validateProperties(); - } + private final List xPaths; - // for testing purposes - MCRGenericPIGenerator(String generalPattern, SimpleDateFormat dateFormat, - String objectTypeMapping, String objectProjectMapping, - int countPrecision, String type, String... xpaths) { - super(); - setObjectProjectMapping(objectProjectMapping); - setGeneralPattern(generalPattern); - setDateFormat(dateFormat); - setObjectTypeMapping(objectTypeMapping); - setCountPrecision(countPrecision); - setType(type); + public MCRGenericPIGenerator(String generalPattern, MCRDateFormatter dateFormatter, + Map projectIdMappings, Map typeIdMappings, + int countPrecision, String type, List xPaths) { + this.generalPattern = Objects.requireNonNull(generalPattern, "General pattern must not be null"); + this.dateFormatter = Objects.requireNonNull(dateFormatter, "Date formatter must not be null"); + this.projectIdMappings = Objects.requireNonNull(projectIdMappings, "Project ID mappings must not be null"); + this.typeIdMappings = Objects.requireNonNull(typeIdMappings, "Type ID mappings must not be null"); + this.countPrecision = countPrecision; + this.type = Objects.requireNonNull(type, "Type must not be null"); + this.xPaths = Objects.requireNonNull(xPaths, "XPaths must not be null"); validateProperties(); - setXpath(xpaths); - } - - private void setXpath(String... xpaths) { - this.xpath = xpaths; } private void validateProperties() { - if (countPrecision == -1 && "dnbUrn".equals(getType())) { + if (countPrecision == -1 && MCRDNBURN.TYPE.equals(type)) { throw new MCRConfigurationException( - PROPERTY_KEY_COUNT_PRECISION + "=-1 and " + PROPERTY_KEY_TYPE + "=urn is not supported!"); + "Combination of count precision -1 and type 'urn' is not supported!"); } } @Override - public MCRPersistentIdentifier generate(MCRBase mcrBase, String additional) + public MCRPersistentIdentifier generate(MCRBase base, String additional) throws MCRPersistentIdentifierException { - String resultingPI = getGeneralPattern(); + String resultingPI = generalPattern; if (resultingPI.contains(PLACE_HOLDER_CURRENT_DATE)) { - resultingPI = resultingPI.replace(PLACE_HOLDER_CURRENT_DATE, getDateFormat().format(new Date())); + resultingPI = resultingPI.replace(PLACE_HOLDER_CURRENT_DATE, dateFormatter.format(new Date())); } if (resultingPI.contains(PLACE_HOLDER_OBJECT_DATE)) { - final Date objectCreateDate = mcrBase.getService().getDate(MCRObjectService.DATE_TYPE_CREATEDATE); - resultingPI = resultingPI.replace(PLACE_HOLDER_OBJECT_DATE, getDateFormat().format(objectCreateDate)); + final Date objectCreateDate = base.getService().getDate(MCRObjectService.DATE_TYPE_CREATEDATE); + if (objectCreateDate == null) { + throw new MCRPersistentIdentifierException("Object " + base.getId() + " doesn't have a create date!"); + } + resultingPI = resultingPI.replace(PLACE_HOLDER_OBJECT_DATE, dateFormatter.format(objectCreateDate)); } - if (resultingPI.contains(PLACE_HOLDER_OBJECT_TYPE)) { - final String mappedObjectType = getMappedType(mcrBase.getId()); - resultingPI = resultingPI.replace(PLACE_HOLDER_OBJECT_TYPE, mappedObjectType); + if (resultingPI.contains(PLACE_HOLDER_OBJECT_PROJECT)) { + final String projectId = base.getId().getProjectId(); + final String mappedProjectId = projectIdMappings.getOrDefault(projectId, projectId); + resultingPI = resultingPI.replace(PLACE_HOLDER_OBJECT_PROJECT, mappedProjectId); } - if (resultingPI.contains(PLACE_HOLDER_OBJECT_PROJECT)) { - final String mappedObjectProject = getMappedProject(mcrBase.getId()); - resultingPI = resultingPI.replace(PLACE_HOLDER_OBJECT_PROJECT, mappedObjectProject); + if (resultingPI.contains(PLACE_HOLDER_OBJECT_TYPE)) { + final String typeId = base.getId().getTypeId(); + final String mappedTypeId = typeIdMappings.getOrDefault(typeId, typeId); + resultingPI = resultingPI.replace(PLACE_HOLDER_OBJECT_TYPE, mappedTypeId); } if (resultingPI.contains(PLACE_HOLDER_OBJECT_NUMBER)) { - resultingPI = resultingPI.replace(PLACE_HOLDER_OBJECT_NUMBER, mcrBase.getId().getNumberAsString()); + resultingPI = resultingPI.replace(PLACE_HOLDER_OBJECT_NUMBER, base.getId().getNumberAsString()); } if (XPATH_PATTERN.asPredicate().test(resultingPI)) { resultingPI = XPATH_PATTERN.matcher(resultingPI).replaceAll((mr) -> { final String xpathNumberString = mr.group(1); final int xpathNumber = Integer.parseInt(xpathNumberString, 10) - 1; - if (this.xpath.length <= xpathNumber || xpathNumber < 0) { + if (this.xPaths.size() <= xpathNumber || xpathNumber < 0) { throw new MCRException( - "The index of " + xpathNumber + " is out of bounds of xpath array (" + xpath.length + ")"); + "The index of " + xpathNumber + " is out of bounds of xpath array (" + xPaths.size() + ")"); } - final String xpathString = this.xpath[xpathNumber]; + final String xpathString = this.xPaths.get(xpathNumber); XPathFactory factory = XPathFactory.instance(); XPathExpression expr = factory.compile(xpathString, Filters.fpassthrough(), null, MCRConstants.getStandardNamespaces()); - final Object content = expr.evaluateFirst(mcrBase.createXML()); + final Object content = expr.evaluateFirst(base.createXML()); return switch (content) { case Text text -> text.getTextNormalize(); case Attribute attribute -> attribute.getValue(); case Element element -> element.getTextNormalize(); - case null, default -> content.toString(); + case null -> ""; + default -> content.toString(); }; }); LOGGER.info(resultingPI); } final MCRPIParser parser = MCRPIManager.getInstance() - .getParserForType(getType()); + .getParserForType(type); String result; result = applyCount(resultingPI); - if (getType().equals("dnbUrn")) { + if (MCRDNBURN.TYPE.equals(type)) { result = result + "C"; // will be replaced by the URN-Parser } @@ -278,19 +290,10 @@ public MCRPersistentIdentifier generate(MCRBase mcrBase, String additional) private String applyCount(String resultingPI) { String result; if (resultingPI.contains(PLACE_HOLDER_COUNT)) { - final int countPrecision = getCountPrecision(); - String regexpStr; - - if (countPrecision == -1) { - regexpStr = "([0-9]+)"; - } else { - regexpStr = "(" - + IntStream.range(0, countPrecision).mapToObj((i) -> "[0-9]").collect(Collectors.joining("")) - + ")"; - } - String counterPattern = resultingPI.replace(PLACE_HOLDER_COUNT, regexpStr); - if (getType().equals("dnbUrn")) { + String counterPattern = resultingPI.replace(PLACE_HOLDER_COUNT, getCountPattern(countPrecision)); + + if (MCRDNBURN.TYPE.equals(type)) { counterPattern = counterPattern + "[0-9]"; } @@ -300,7 +303,8 @@ private String applyCount(String resultingPI) { LOGGER.info("Count is {}", count); final String pattern = IntStream.range(0, Math.abs(countPrecision)).mapToObj((i) -> "0") .collect(Collectors.joining("")); - DecimalFormat decimalFormat = new DecimalFormat(pattern, DecimalFormatSymbols.getInstance(Locale.ROOT)); + DecimalFormat decimalFormat = + new DecimalFormat(pattern, DecimalFormatSymbols.getInstance(Locale.ROOT)); final String countAsString = countPrecision != -1 ? decimalFormat.format(count) : String.valueOf(count); result = resultingPI.replace(PLACE_HOLDER_COUNT, countAsString); } else { @@ -309,117 +313,56 @@ private String applyCount(String resultingPI) { return result; } - private String getMappedType(MCRObjectID id) { - String mapping = getObjectTypeMapping(); - String typeID = id.getTypeId(); - - return Optional.ofNullable(mapping) - .map(mappingStr -> mappingStr.split(",")) - .map(Arrays::asList) - .filter(o -> o.getFirst().equals(typeID)) - .map(o -> o.get(1)) - .orElse(typeID); - } - - private String getMappedProject(MCRObjectID id) { - String mapping = getObjectProjectMapping(); - String projectID = id.getProjectId(); - - return Optional.ofNullable(mapping) - .map(mappingStr -> mappingStr.split(",")) - .map(Arrays::asList) - .filter(o -> o.getFirst().equals(projectID)) - .map(o -> o.get(1)) - .orElse(projectID); - } - - protected AtomicInteger readCountFromDatabase(String countPattern) { - Pattern regExpPattern = Pattern.compile(countPattern); - Predicate matching = regExpPattern.asPredicate(); - - List list = MCRPIManager.getInstance() - .getList(getType(), -1, -1); - - // extract the number of the PI - Optional highestNumber = list.stream() - .map(MCRPIRegistrationInfo::getIdentifier) - .filter(matching) - .map(pi -> { - // extract the number of the PI - Matcher matcher = regExpPattern.matcher(pi); - if (matcher.find() && matcher.groupCount() == 1) { - String group = matcher.group(1); - return Integer.parseInt(group, 10); - } else { - return null; - } - }).filter(Objects::nonNull) - .min(Comparator.reverseOrder()) - .map(n -> n + 1); - return new AtomicInteger(highestNumber.orElse(0)); - } - - private String getType() { - return type; - } - - public void setType(String type) { - this.type = type; + /** + * Gets the count for a specific pattern and increase the internal counter. If there is no internal counter it will + * look into the Database and detect the highest count with the pattern. + * + * @param pattern a regex pattern which will be used to detect the highest count. The first group is the count. + * e.G. [0-9]+-mods-2017-([0-9][0-9][0-9][0-9])-[0-9] will match 31-mods-2017-0003-3 and the returned + * count will be 4 (3+1). + * @return the next count + */ + public final synchronized int getCount(String pattern) { + return PATTERN_COUNT_MAP + .computeIfAbsent(pattern, p -> readCountFromDatabase(type, p)) + .getAndIncrement(); } - public String getGeneralPattern() { - return generalPattern; - } + public static class Factory implements Supplier { - public void setGeneralPattern(String generalPattern) { - this.generalPattern = generalPattern; - } + @MCRProperty(name = GENERAL_PATTERN_KEY) + public String generalPattern; - public SimpleDateFormat getDateFormat() { - return dateFormat; - } + @MCRInstance(name = DATE_FORMATTER_KEY, valueClass = MCRDateFormatter.class, required = false) + public MCRDateFormatter dateFormatter; - public void setDateFormat(SimpleDateFormat dateFormat) { - this.dateFormat = dateFormat; - } + @MCRPropertyMap(name = OBJECT_PROJECT_MAPPING_KEY, required = false) + public Map projectIdMappings; - public String getObjectTypeMapping() { - return objectTypeMapping; - } + @MCRPropertyMap(name = OBJECT_TYPE_MAPPING_KEY, required = false) + public Map typeIdMappings; - public void setObjectTypeMapping(String typeMapping) { - this.objectTypeMapping = typeMapping; - } + @MCRProperty(name = COUNT_PRECISION_KEY, required = false) + public String countPrecision = "-1"; - public int getCountPrecision() { - return countPrecision; - } + @MCRProperty(name = TYPE_KEY) + public String type; - public void setCountPrecision(int countPrecision) { - this.countPrecision = countPrecision; - } + @MCRPropertyList(name = X_PATH_KEY, required = false) + public List xPaths; - /** - * Gets the count for a specific pattern and increase the internal counter. If there is no internal counter it will - * look into the Database and detect the highest count with the pattern. - * - * @param pattern a reg exp pattern which will be used to detect the highest count. The first group is the count. - * e.G. [0-9]+-mods-2017-([0-9][0-9][0-9][0-9])-[0-9] will match 31-mods-2017-0003-3 and the returned - * count will be 4 (3+1). - * @return the next count - */ - public final synchronized int getCount(String pattern) { - AtomicInteger count = PATTERN_COUNT_MAP - .computeIfAbsent(pattern, this::readCountFromDatabase); + @Override + public MCRGenericPIGenerator get() { + return new MCRGenericPIGenerator(generalPattern, getFormatter(), + projectIdMappings, typeIdMappings, + Integer.parseInt(countPrecision), type, xPaths); + } - return count.getAndIncrement(); - } + private MCRDateFormatter getFormatter() { + return dateFormatter != null ? dateFormatter + : new MCRSimpleDateFormatter(DEFAULT_DATE_FORMAT, DEFAULT_DATE_LOCALE); + } - public String getObjectProjectMapping() { - return objectProjectMapping; } - public void setObjectProjectMapping(String objectProjectMapping) { - this.objectProjectMapping = objectProjectMapping; - } } diff --git a/mycore-pi/src/main/java/org/mycore/pi/MCRPICreationEventHandler.java b/mycore-pi/src/main/java/org/mycore/pi/MCRPICreationEventHandler.java index 53197da451..2ff68d2d3d 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/MCRPICreationEventHandler.java +++ b/mycore-pi/src/main/java/org/mycore/pi/MCRPICreationEventHandler.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.concurrent.ExecutionException; import java.util.function.Predicate; -import java.util.stream.Collectors; import org.mycore.access.MCRAccessException; import org.mycore.common.MCRException; @@ -45,24 +44,32 @@ protected void handleObjectUpdated(MCREvent evt, MCRObject obj) { private void processPIServices(MCRObject obj) { List registered = MCRPIManager.getInstance().getRegistered(obj); - final List services = registered.stream().map(MCRPIRegistrationInfo::getService) - .collect(Collectors.toList()); + final List services = registered.stream().map(MCRPIRegistrationInfo::getService).toList(); - List> listOfServicesWithCreatablePIs = MCRPIServiceManager - .getInstance().getAutoCreationList().stream() - .filter(Predicate.not(s -> services.contains(s.getServiceID()))) - .filter(Predicate.not(s -> MCRPIService.hasFlag(obj, "", s))) - .filter(s -> s.getCreationPredicate().test(obj)) - .collect(Collectors.toList()); + List> autoCreatingPIServices = MCRPIServiceManager + .getInstance().getAutoCreationList(); - listOfServicesWithCreatablePIs - .forEach((serviceToRegister) -> { + boolean mayCreatePI = true; + while (mayCreatePI) { + + mayCreatePI = false; + + List> listOfServicesWithCreatablePIs = autoCreatingPIServices.stream() + .filter(Predicate.not(s -> services.contains(s.getServiceID()))) + .filter(Predicate.not(s -> MCRPIService.hasFlag(obj, "", s))) + .filter(s -> s.getCreationPredicate().test(obj)) + .toList(); + + for (MCRPIService serviceToRegister : listOfServicesWithCreatablePIs) { try { serviceToRegister.register(obj, "", false); + mayCreatePI = true; } catch (MCRAccessException | MCRPersistentIdentifierException | ExecutionException | InterruptedException e) { throw new MCRException("Error while register pi for object " + obj.getId().toString(), e); } - }); + } + } + } } diff --git a/mycore-pi/src/main/java/org/mycore/pi/MCRPIGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/MCRPIGenerator.java index ee9f300d13..85cc2b962e 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/MCRPIGenerator.java +++ b/mycore-pi/src/main/java/org/mycore/pi/MCRPIGenerator.java @@ -18,59 +18,19 @@ package org.mycore.pi; -import static org.mycore.pi.MCRPIService.GENERATOR_CONFIG_PREFIX; - -import java.util.Map; - -import org.mycore.common.config.MCRConfigurationException; -import org.mycore.common.config.annotation.MCRPostConstruction; -import org.mycore.common.config.annotation.MCRRawProperties; import org.mycore.datamodel.metadata.MCRBase; import org.mycore.pi.exceptions.MCRPersistentIdentifierException; -public abstract class MCRPIGenerator { - - private String generatorID; - - private Map properties; - - public final Map getProperties() { - return properties; - } - - @MCRPostConstruction - public void init(String property) { - generatorID = property.substring(GENERATOR_CONFIG_PREFIX.length()); - } - - @MCRRawProperties(namePattern = "*", required = false) - public void setProperties(Map properties) { - this.properties = properties; - } +public interface MCRPIGenerator { /** * generates a {@link MCRPersistentIdentifier} * - * @param mcrBase the mycore object for which the identifier is generated + * @param base the mycore object for which the identifier is generated * @param additional additional information dedicated to the object like a mcrpath * @return a unique persistence identifier * @throws MCRPersistentIdentifierException if something goes wrong while generating */ - public abstract T generate(MCRBase mcrBase, String additional) throws MCRPersistentIdentifierException; - - /** - * checks if the property exists and throws a exception if not. - * @param propertyName to check - * @throws MCRConfigurationException if property does not exist - */ - protected void checkPropertyExists(final String propertyName) throws MCRConfigurationException { - if (!getProperties().containsKey(propertyName)) { - throw new MCRConfigurationException( - "Missing property " + GENERATOR_CONFIG_PREFIX + getGeneratorID() + "." + propertyName); - } - } + T generate(MCRBase base, String additional) throws MCRPersistentIdentifierException; - public String getGeneratorID() { - return generatorID; - } } diff --git a/mycore-pi/src/main/java/org/mycore/pi/doi/MCRCreateDateDOIGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/doi/MCRCreateDateDOIGenerator.java index 0712d41472..01ed80f440 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/doi/MCRCreateDateDOIGenerator.java +++ b/mycore-pi/src/main/java/org/mycore/pi/doi/MCRCreateDateDOIGenerator.java @@ -18,96 +18,124 @@ package org.mycore.pi.doi; -import java.util.Comparator; -import java.util.Date; +import static org.mycore.pi.util.MCRPIGeneratorUtils.formatCount; +import static org.mycore.pi.util.MCRPIGeneratorUtils.getCountPattern; +import static org.mycore.pi.util.MCRPIGeneratorUtils.getCreateDate; +import static org.mycore.pi.util.MCRPIGeneratorUtils.readCountFromDatabase; + import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Predicate; -import java.util.regex.Matcher; +import java.util.function.Supplier; import java.util.regex.Pattern; -import org.mycore.common.MCRException; -import org.mycore.common.MCRPersistenceException; -import org.mycore.common.config.MCRConfiguration2; -import org.mycore.datamodel.common.MCRISO8601Date; +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRInstance; +import org.mycore.common.config.annotation.MCRProperty; +import org.mycore.common.date.MCRDateFormatter; +import org.mycore.common.date.MCRISO8601DateFormatter; import org.mycore.datamodel.metadata.MCRBase; -import org.mycore.datamodel.metadata.MCRObjectService; +import org.mycore.pi.MCRGenericPIGenerator; import org.mycore.pi.MCRPIGenerator; -import org.mycore.pi.MCRPIManager; -import org.mycore.pi.MCRPIRegistrationInfo; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; + +/** + * {@link MCRCreateDateDOIGenerator} is a {@link MCRPIGenerator} for {@link MCRDigitalObjectIdentifier} identifiers + * that generates identifiers using a given prefix and the current date and a per-date counter for the suffix. + *

    + * The following configuration options are available: + *

      + *
    • The property suffix {@link MCRGenericPIGenerator#DATE_FORMATTER_KEY} can be used to + * specify the date formatter to be used (optional, defaults to {@link MCRISO8601DateFormatter} with format + * {@link MCRCreateDateDOIGenerator#DEFAULT_DATE_FORMAT} and locale + * {@link MCRCreateDateDOIGenerator#DEFAULT_DATE_LOCALE}). + *
    • The property suffix {@link MCRCreateDateDOIGenerator#PREFIX_KEY} can be used to + * specify the prefix. + *
    • The property suffix {@link MCRCreateDateDOIGenerator#COUNT_PRECISION_KEY} can be used to + * specify number of digits to be used for the count (optional, defaults to -1, + * which uses the natural number of digits). + *
    + * Example: + *
    
    + * [...].Class=org.mycore.pi.doi.MCRCreateDateDOIGenerator
    + * [...].DateFormatter.Class=org.mycore.common.date.MCRSimpleDateFormatter
    + * [...].DateFormatter.Format=yyyy-MM-dd
    + * [...].Prefix=10.1234
    + * [...].CountPrecision=6
    + * 
    + */ +@MCRConfigurationProxy(proxyClass = MCRCreateDateDOIGenerator.Factory.class) +public class MCRCreateDateDOIGenerator extends MCRDOIGeneratorBase { + + public static final String DEFAULT_DATE_FORMAT = "yyyyMMdd-HHmmss"; -public class MCRCreateDateDOIGenerator extends MCRPIGenerator { + public static final Locale DEFAULT_DATE_LOCALE = Locale.ENGLISH; - private static final String DATE_PATTERN = "yyyyMMdd-HHmmss"; + public static final String DATE_FORMATTER_KEY = "Formatter"; - private static final String CREATE_DATE_PLACEHOLDER = "$createDate$"; + public static final String PREFIX_KEY = "Prefix"; - private static final String DATE_REGEXP = CREATE_DATE_PLACEHOLDER + "-([0-9]+)"; + public static final String COUNT_PRECISION_KEY = "CountPrecision"; private static final Map PATTERN_COUNT_MAP = new HashMap<>(); - private final MCRDOIParser mcrdoiParser; + private final MCRDateFormatter dateFormatter; + + private final String prefix; + + private final int countPrecision; - private final String prefix = MCRConfiguration2.getStringOrThrow("MCR.DOI.Prefix"); + private final String countPattern; - public MCRCreateDateDOIGenerator() { - super(); - mcrdoiParser = new MCRDOIParser(); + public MCRCreateDateDOIGenerator(MCRDOIParser parser, MCRDateFormatter dateFormatter, String prefix, + int countPrecision) { + super(parser); + this.dateFormatter = Objects.requireNonNull(dateFormatter, "Date formatter must not be null"); + this.prefix = Objects.requireNonNull(prefix, "Prefix must not be null"); + this.countPrecision = countPrecision; + this.countPattern = getCountPattern(countPrecision); } @Override - public MCRDigitalObjectIdentifier generate(MCRBase mcrObj, String additional) { - Date createdate = mcrObj.getService().getDate(MCRObjectService.DATE_TYPE_CREATEDATE); - if (createdate != null) { - MCRISO8601Date mcrdate = new MCRISO8601Date(); - mcrdate.setDate(createdate); - String createDate = mcrdate.format(DATE_PATTERN, Locale.ENGLISH); - final int count = getCountForCreateDate(createDate); - Optional parse = mcrdoiParser.parse(prefix + "/" + createDate + "-" + count); - return parse.orElseThrow(() -> new MCRException("Error while parsing default doi!")); - } else { - throw new MCRPersistenceException("The object " + mcrObj.getId() + " doesn't have a createdate!"); - } - } + protected String buildDOI(MCRBase base, String additional) throws MCRPersistentIdentifierException { + + String prefixWithDate = prefix + "/" + dateFormatter.format(getCreateDate(base)) + "-"; + int count = getCount(Pattern.quote(prefixWithDate) + countPattern); + + return prefixWithDate + formatCount(count, countPrecision); - private int getCountForCreateDate(String createDate) { - return getCount(prefix + "/" + DATE_REGEXP.replace(CREATE_DATE_PLACEHOLDER, createDate)); } private synchronized int getCount(final String pattern) { - AtomicInteger count = PATTERN_COUNT_MAP.computeIfAbsent(pattern, p -> { - Pattern regExpPattern = Pattern.compile(p); - Predicate matching = regExpPattern.asPredicate(); - - List list = MCRPIManager.getInstance() - .getList(MCRDigitalObjectIdentifier.TYPE, -1, -1); - - Comparator integerComparator = Integer::compareTo; - // extract the number of the PI - Optional highestNumber = list.stream() - .map(MCRPIRegistrationInfo::getIdentifier) - .filter(matching) - .map(pi -> { - // extract the number of the PI - Matcher matcher = regExpPattern.matcher(pi); - if (matcher.find() && matcher.groupCount() == 1) { - String group = matcher.group(1); - return Integer.parseInt(group, 10); - } else { - return null; - } - }).filter(Objects::nonNull) - .max(integerComparator) - .map(n -> n + 1); - return new AtomicInteger(highestNumber.orElse(0)); - }); - - return count.getAndIncrement(); + return PATTERN_COUNT_MAP + .computeIfAbsent(pattern, p -> readCountFromDatabase(MCRDigitalObjectIdentifier.TYPE, p)) + .getAndIncrement(); + } + + public static class Factory implements Supplier { + + @MCRInstance(name = DATE_FORMATTER_KEY, valueClass = MCRDateFormatter.class, required = false) + public MCRDateFormatter dateFormatter; + + @MCRProperty(name = PREFIX_KEY, defaultName = "MCR.DOI.Prefix") + public String prefix; + + @MCRProperty(name = COUNT_PRECISION_KEY, required = false) + public String countPrecision = "-1"; + + @Override + public MCRCreateDateDOIGenerator get() { + return new MCRCreateDateDOIGenerator(new MCRDOIParser(), getDateFormatter(), prefix, + Integer.parseInt(countPrecision)); + } + + private MCRDateFormatter getDateFormatter() { + return dateFormatter != null ? dateFormatter + : new MCRISO8601DateFormatter(DEFAULT_DATE_FORMAT, DEFAULT_DATE_LOCALE); + } + } } diff --git a/mycore-pi/src/main/java/org/mycore/pi/doi/MCRCurrentDateDOIGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/doi/MCRCurrentDateDOIGenerator.java new file mode 100644 index 0000000000..b7fa841607 --- /dev/null +++ b/mycore-pi/src/main/java/org/mycore/pi/doi/MCRCurrentDateDOIGenerator.java @@ -0,0 +1,112 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.doi; + +import java.util.Date; +import java.util.Objects; +import java.util.function.Supplier; + +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRInstance; +import org.mycore.common.config.annotation.MCRProperty; +import org.mycore.common.date.MCRDateFormatter; +import org.mycore.common.date.MCRFLDateScrambler; +import org.mycore.datamodel.metadata.MCRBase; +import org.mycore.pi.MCRPIGenerator; + +/** + * {@link MCRCurrentDateDOIGenerator} is a {@link MCRPIGenerator} for {@link MCRDigitalObjectIdentifier} identifiers + * that generates identifiers using a given prefix and the current date (in seconds) value as the suffix. + *

    + * Only one suffix per second will be generated. + *

    + * The following configuration options are available: + *

      + *
    • The property suffix {@link MCRCurrentDateDOIGenerator#DATE_FORMATTER_KEY} can be used to + * specify the date formatter to be used (optional, defaults to {@link MCRFLDateScrambler}). + *
    • The property suffix {@link MCRCurrentDateDOIGenerator#PREFIX_KEY} can be used to + * specify the prefix. + *
    + * Example: + *
    
    + * [...].Class=org.mycore.pi.doi.MCRCurrentDateDOIGenerator
    + * [...].DateFormatter.Class=org.mycore.common.date.MCRSimpleDateFormatter
    + * [...].DateFormatter.Format=yyyy-MM-dd
    + * [...].Prefix=10.1234
    + * 
    + */ +@MCRConfigurationProxy(proxyClass = MCRCurrentDateDOIGenerator.Factory.class) +public class MCRCurrentDateDOIGenerator extends MCRDOIGeneratorBase { + + public static final String DATE_FORMATTER_KEY = "DateFormatter"; + + public static final String PREFIX_KEY = "Prefix"; + + private final String prefix; + + private final MCRDateFormatter dateFormatter; + + private String lastSuffix; + + public MCRCurrentDateDOIGenerator(MCRDOIParser parser, MCRDateFormatter dateFormatter, String prefix) { + super(parser); + this.dateFormatter = Objects.requireNonNull(dateFormatter, "Date formatter must not be null"); + this.prefix = Objects.requireNonNull(prefix, "Prefix must not be null"); + } + + @Override + protected synchronized String buildDOI(MCRBase base, String additional) { + + Date date = new Date((System.currentTimeMillis() / 1000) * 1000); + String suffix = dateFormatter.format(date); + + if (suffix.equals(lastSuffix)) { + try { + Thread.sleep(500); + } catch (InterruptedException ignored) { + } + return buildDOI(base, additional); + } + + lastSuffix = suffix; + + return prefix + "/" + suffix; + + } + + public static class Factory implements Supplier { + + @MCRInstance(name = DATE_FORMATTER_KEY, valueClass = MCRDateFormatter.class, required = false) + public MCRDateFormatter dateFormatter; + + @MCRProperty(name = PREFIX_KEY, defaultName = "MCR.DOI.Prefix") + public String prefix; + + @Override + public MCRCurrentDateDOIGenerator get() { + return new MCRCurrentDateDOIGenerator(new MCRDOIParser(), getDateFormatter(), prefix); + } + + private MCRDateFormatter getDateFormatter() { + return dateFormatter != null ? dateFormatter : new MCRFLDateScrambler(); + } + + } + +} diff --git a/mycore-pi/src/main/java/org/mycore/pi/doi/MCRStaticDOIGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/doi/MCRDOIGeneratorBase.java similarity index 55% rename from mycore-pi/src/main/java/org/mycore/pi/doi/MCRStaticDOIGenerator.java rename to mycore-pi/src/main/java/org/mycore/pi/doi/MCRDOIGeneratorBase.java index ff8132d985..afa4a603e0 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/doi/MCRStaticDOIGenerator.java +++ b/mycore-pi/src/main/java/org/mycore/pi/doi/MCRDOIGeneratorBase.java @@ -18,28 +18,27 @@ package org.mycore.pi.doi; -import java.util.Optional; +import java.util.Objects; -import org.mycore.common.config.MCRConfigurationException; import org.mycore.datamodel.metadata.MCRBase; import org.mycore.pi.MCRPIGenerator; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; -/** - * Just for testing. Provides the doi in the doi property. - */ -public class MCRStaticDOIGenerator extends MCRPIGenerator { +public abstract class MCRDOIGeneratorBase implements MCRPIGenerator { + + private final MCRDOIParser parser; - public MCRStaticDOIGenerator() { - super(); - checkPropertyExists("doi"); + public MCRDOIGeneratorBase(MCRDOIParser parser) { + this.parser = Objects.requireNonNull(parser, "Parser must not be null"); } @Override - public MCRDigitalObjectIdentifier generate(MCRBase mcrBase, String additional) { - final String doi = getProperties().get("doi"); - final Optional generatedDOI = new MCRDOIParser().parse(doi); - return generatedDOI - .orElseThrow(() -> new MCRConfigurationException("The property " + doi + " is not a valid doi!")); + public final MCRDigitalObjectIdentifier generate(MCRBase base, String additional) + throws MCRPersistentIdentifierException { + return parser.parse(buildDOI(base, additional)).get(); } + protected abstract String buildDOI(MCRBase base, String additional) + throws MCRPersistentIdentifierException; + } diff --git a/mycore-pi/src/main/java/org/mycore/pi/doi/MCRMapObjectIDDOIGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/doi/MCRMapObjectIDDOIGenerator.java index fe239b7dd4..9abcf89329 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/doi/MCRMapObjectIDDOIGenerator.java +++ b/mycore-pi/src/main/java/org/mycore/pi/doi/MCRMapObjectIDDOIGenerator.java @@ -18,44 +18,73 @@ package org.mycore.pi.doi; -import java.util.Optional; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRPropertyMap; import org.mycore.datamodel.metadata.MCRBase; import org.mycore.datamodel.metadata.MCRObjectID; import org.mycore.pi.MCRPIGenerator; import org.mycore.pi.exceptions.MCRPersistentIdentifierException; /** - * Uses mapping from MCRObjectID base to DOI prefix to generate DOIs. - * e.g. MCR.PI.Generator.MapObjectIDDOI.Prefix.mycore_mods = 10.5072/my. will map - * mycore_mods_00004711 to 10.5072/my.4711 - * - * @author Thomas Scheffler (yagee) + * {@link MCRMapObjectIDDOIGenerator} is a {@link MCRPIGenerator} for {@link MCRDigitalObjectIdentifier} identifiers + * that generates identifiers by concatenating a {@link MCRObjectID#getBase}-dependent value and the numerical + * part of the {@link MCRObjectID} of the {@link MCRBase}. + *

    + * Example: Prefix mapping mycore_mods=10.1234/MODS. will map mycore_mods_00000123 + * to 10.1234/MODS.123 + *

    + * The following configuration options are available: + *

      + *
    • The property suffix {@link MCRMapObjectIDDOIGenerator#PREFIX_KEY} can be used to + * specify the prefix mappings to be used. + *
    + * Example: + *
    
    + * [...].Class=org.mycore.pi.doi.MCRMapObjectIDDOIGenerator
    + * [...].Prefix.mycore_mods=10.1234/MODS.
    + * [...].Prefix.mycore_alto=10.9876/ALTO.
    + * 
    */ -public class MCRMapObjectIDDOIGenerator extends MCRPIGenerator { +@MCRConfigurationProxy(proxyClass = MCRMapObjectIDDOIGenerator.Factory.class) +public class MCRMapObjectIDDOIGenerator extends MCRDOIGeneratorBase { - private final MCRDOIParser mcrdoiParser; + public static final String PREFIX_KEY = "Prefix"; - private String generatorID; + private final Map prefixMap; - public MCRMapObjectIDDOIGenerator() { - super(); - mcrdoiParser = new MCRDOIParser(); + public MCRMapObjectIDDOIGenerator(MCRDOIParser parser, Map prefixMap) { + super(parser); + this.prefixMap = Objects.requireNonNull(prefixMap, "Prefix map must not be null"); } @Override - public MCRDigitalObjectIdentifier generate(MCRBase mcrObject, String additional) - throws MCRPersistentIdentifierException { - final MCRObjectID objectId = mcrObject.getId(); - return Optional.ofNullable(getProperties().get("Prefix." + objectId.getBase())) - .map(prefix -> { - final int objectIdNumberAsInteger = objectId.getNumberAsInteger(); - return prefix.contains("/") ? prefix + objectIdNumberAsInteger - : prefix + '/' + objectIdNumberAsInteger; - }) - .flatMap(mcrdoiParser::parse).map(MCRDigitalObjectIdentifier.class::cast) - .orElseThrow(() -> new MCRPersistentIdentifierException("Prefix." + objectId.getBase() + - " is not defined in " + generatorID + ".")); + protected String buildDOI(MCRBase base, String additional) throws MCRPersistentIdentifierException { + + MCRObjectID objectId = base.getId(); + String prefix = prefixMap.get(objectId.getBase()); + + if (prefix == null) { + throw new MCRPersistentIdentifierException("Missing prefix for base " + objectId.getBase()); + } + + int objectIdNumberAsInteger = objectId.getNumberAsInteger(); + return prefix.contains("/") ? prefix + objectIdNumberAsInteger : prefix + '/' + objectIdNumberAsInteger; + } + public static class Factory implements Supplier { + + @MCRPropertyMap(name = PREFIX_KEY) + public Map prefixMap; + + @Override + public MCRMapObjectIDDOIGenerator get() { + return new MCRMapObjectIDDOIGenerator(new MCRDOIParser(), prefixMap); + } + + } } diff --git a/mycore-pi/src/main/java/org/mycore/pi/doi/MCROtherPIDOIGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/doi/MCROtherPIDOIGenerator.java new file mode 100644 index 0000000000..826caf99d4 --- /dev/null +++ b/mycore-pi/src/main/java/org/mycore/pi/doi/MCROtherPIDOIGenerator.java @@ -0,0 +1,103 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.doi; + +import java.util.Objects; +import java.util.function.Supplier; + +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; +import org.mycore.datamodel.metadata.MCRBase; +import org.mycore.pi.MCRPIGenerator; +import org.mycore.pi.util.MCROtherPIValueExtractor; + +/** + * {@link MCROtherPIDOIGenerator} is a {@link MCRPIGenerator} for {@link MCRDigitalObjectIdentifier} identifiers + * that generates identifiers using a given prefix and a value extracted from another PI + * that is already assigned as the suffix. + *

    + * The following configuration options are available: + *

      + *
    • The property suffix {@link MCROtherPIDOIGenerator#PREFIX_KEY} can be used to + * specify the prefix. + *
    • The property suffix {@link MCROtherPIDOIGenerator#TYPE_KEY} can be used to + * specify the type of the assigned PI. + *
    • The property suffix {@link MCROtherPIDOIGenerator#SERVICE_KEY} can be used to + * specify the service of the assigned PI. + *
    • The property suffix {@link MCROtherPIDOIGenerator#PATTERN_KEY} can be used to + * specify the pattern (must contain a single capture group). + *
    + * Example: + *
    
    + * [...].Class=org.mycore.pi.doi.MCROtherPIDOIGenerator
    + * [...].Prefix=10.1234
    + * [...].Type=dnbUrn
    + * [...].Service=DNBURN
    + * [...].Pattern=urn:nbn:de:xzy-(.+)-[0-9]
    + * 
    + */ +@MCRConfigurationProxy(proxyClass = MCROtherPIDOIGenerator.Factory.class) +public class MCROtherPIDOIGenerator extends MCRDOIGeneratorBase { + + public static final String TYPE_KEY = "Type"; + + public static final String SERVICE_KEY = "Service"; + + public static final String PATTERN_KEY = "Pattern"; + + public static final String PREFIX_KEY = "Prefix"; + + private final String prefix; + + private final MCROtherPIValueExtractor extractor; + + public MCROtherPIDOIGenerator(MCRDOIParser parser, String prefix, MCROtherPIValueExtractor extractor) { + super(parser); + this.prefix = Objects.requireNonNull(prefix, "Prefix must not be null"); + this.extractor = Objects.requireNonNull(extractor, "Extractor must not be null"); + } + + @Override + protected String buildDOI(MCRBase base, String additional) { + return prefix + "/" + extractor.extractValue(base.getId()); + } + + public static class Factory implements Supplier { + + @MCRProperty(name = PREFIX_KEY, defaultName = "MCR.DOI.Prefix") + public String prefix; + + @MCRProperty(name = TYPE_KEY) + public String type; + + @MCRProperty(name = SERVICE_KEY) + public String service; + + @MCRProperty(name = PATTERN_KEY) + public String pattern; + + @Override + public MCROtherPIDOIGenerator get() { + MCROtherPIValueExtractor extractor = new MCROtherPIValueExtractor(type, service, pattern); + return new MCROtherPIDOIGenerator(new MCRDOIParser(), prefix, extractor); + } + + } + +} diff --git a/mycore-pi/src/main/java/org/mycore/pi/doi/MCRUUIDDOIGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/doi/MCRUUIDDOIGenerator.java index b686ee1805..7b88fe60b7 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/doi/MCRUUIDDOIGenerator.java +++ b/mycore-pi/src/main/java/org/mycore/pi/doi/MCRUUIDDOIGenerator.java @@ -18,29 +18,57 @@ package org.mycore.pi.doi; -import java.util.Optional; +import java.util.Objects; import java.util.UUID; +import java.util.function.Supplier; -import org.mycore.common.config.MCRConfiguration2; +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; import org.mycore.datamodel.metadata.MCRBase; import org.mycore.pi.MCRPIGenerator; -import org.mycore.pi.MCRPersistentIdentifier; -public class MCRUUIDDOIGenerator extends MCRPIGenerator { +/** + * {@link MCRUUIDDOIGenerator} is a {@link MCRPIGenerator} for {@link MCRDigitalObjectIdentifier} identifiers + * that generates identifiers using a given prefix and a {@link UUID} as the suffix. + *

    + * The following configuration options are available: + *

      + *
    • The property suffix {@link MCRUUIDDOIGenerator#PREFIX_KEY} can be used to + * specify the prefix. + *
    + * Example: + *
    
    + * [...].Class=org.mycore.pi.doi.MCRUUIDDOIGenerator
    + * [...].Prefix=10.1234
    + * 
    + */ +@MCRConfigurationProxy(proxyClass = MCRUUIDDOIGenerator.Factory.class) +public class MCRUUIDDOIGenerator extends MCRDOIGeneratorBase { - private final MCRDOIParser mcrdoiParser; + public static final String PREFIX_KEY = "Prefix"; - private String prefix = MCRConfiguration2.getStringOrThrow("MCR.DOI.Prefix"); + private final String prefix; - public MCRUUIDDOIGenerator() { - super(); - mcrdoiParser = new MCRDOIParser(); + public MCRUUIDDOIGenerator(MCRDOIParser parser, String prefix) { + super(parser); + this.prefix = Objects.requireNonNull(prefix, "Prefix must not be null"); } @Override - public MCRDigitalObjectIdentifier generate(MCRBase mcrObject, String additional) { - Optional parse = mcrdoiParser.parse(prefix + "/" + UUID.randomUUID()); - MCRPersistentIdentifier doi = parse.get(); - return (MCRDigitalObjectIdentifier) doi; + protected String buildDOI(MCRBase base, String additional) { + return prefix + "/" + UUID.randomUUID(); } + + public static class Factory implements Supplier { + + @MCRProperty(name = PREFIX_KEY, defaultName = "MCR.DOI.Prefix") + public String prefix; + + @Override + public MCRUUIDDOIGenerator get() { + return new MCRUUIDDOIGenerator(new MCRDOIParser(), prefix); + } + + } + } diff --git a/mycore-pi/src/main/java/org/mycore/pi/purl/MCRIDPURLGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/purl/MCRIDPURLGenerator.java index 14cdb4fb24..0e5bbc1ecd 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/purl/MCRIDPURLGenerator.java +++ b/mycore-pi/src/main/java/org/mycore/pi/purl/MCRIDPURLGenerator.java @@ -21,37 +21,77 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; +import java.util.Objects; +import java.util.function.Supplier; +import java.util.regex.Pattern; +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; import org.mycore.datamodel.metadata.MCRBase; +import org.mycore.datamodel.metadata.MCRObjectID; import org.mycore.pi.MCRPIGenerator; import org.mycore.pi.exceptions.MCRPersistentIdentifierException; /** - * The property BaseURLTemplate is your url and the {@link MCRIDPURLGenerator} - * will replace $ID with the actual MyCoRe-ID. + * {@link MCRIDPURLGenerator} is a {@link MCRPIGenerator} for {@link MCRPURL} identifiers + * that generates identifiers using a template and the {@link MCRObjectID} of the {@link MCRBase}. + *

    + *

    + * The following configuration options are available: + *

      + *
    • The property suffix {@link MCRIDPURLGenerator#BASE_URL_TEMPLATE_KEY} can be used to + * specify the template (must contain the replacement marker $ID). + *
    + * Example: + *
    
    + * [...].Class=org.mycore.pi.purl.MCRIDPURLGenerator
    + * [...].BaseURLTemplate=https://purl.example.com/$ID
    + * 
    */ -public class MCRIDPURLGenerator extends MCRPIGenerator { +@MCRConfigurationProxy(proxyClass = MCRIDPURLGenerator.Factory.class) +public class MCRIDPURLGenerator implements MCRPIGenerator { - @Override - public MCRPURL generate(MCRBase mcrObj, String additional) - throws MCRPersistentIdentifierException { + public static final String BASE_URL_TEMPLATE_KEY = "BaseURLTemplate"; + + private final String baseUrlTemplate; + + public MCRIDPURLGenerator(String baseUrlTemplate) { + Objects.requireNonNull(baseUrlTemplate, "Base URL template must not be null"); + this.baseUrlTemplate = checkBaseUrlTemplate(baseUrlTemplate); + } - String baseUrlTemplate = getProperties().getOrDefault("BaseURLTemplate", ""); + private static String checkBaseUrlTemplate(String baseUrlTemplate) { + if (!baseUrlTemplate.contains("$ID")) { + throw new IllegalArgumentException("Base URL template doesn't contain replacement marker $ID: " + + baseUrlTemplate); + } + return baseUrlTemplate; + } - String replace = baseUrlTemplate; - String before; + @Override + public MCRPURL generate(MCRBase base, String additional) throws MCRPersistentIdentifierException { - do { - before = replace; - replace = baseUrlTemplate.replace("$ID", mcrObj.getId().toString()); - } while (!replace.equals(before)); + String id = base.getId().toString(); + String baseUrl = baseUrlTemplate.replaceAll(Pattern.quote("$ID"), id); try { - URL url = new URI(replace).toURL(); - return new MCRPURL(url); + return new MCRPURL(new URI(baseUrl).toURL()); } catch (MalformedURLException | URISyntaxException e) { - throw new MCRPersistentIdentifierException("Error while creating URL object from string: " + replace, e); + throw new MCRPersistentIdentifierException("Error while creating base URL for " + id + ": " + baseUrl, e); } + } + + public static class Factory implements Supplier { + + @MCRProperty(name = BASE_URL_TEMPLATE_KEY) + public String baseUrlTemplate; + + @Override + public MCRIDPURLGenerator get() { + return new MCRIDPURLGenerator(baseUrlTemplate); + } + + } + } diff --git a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCountingDNBURNGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCountingDNBURNGenerator.java index 274cba8032..baaa828b8e 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCountingDNBURNGenerator.java +++ b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCountingDNBURNGenerator.java @@ -18,70 +18,14 @@ package org.mycore.pi.urn; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.mycore.pi.MCRPIManager; -import org.mycore.pi.MCRPIRegistrationInfo; - /** - * A Generator which helps to generate a URN with a counter inside. + * @deprecated Use {@link MCRCountingDNBURNGeneratorBase} instead. */ -public abstract class MCRCountingDNBURNGenerator extends MCRDNBURNGenerator { - - private static final Map PATTERN_COUNT_MAP = new HashMap<>(); +@Deprecated(forRemoval = true) +public abstract class MCRCountingDNBURNGenerator extends MCRCountingDNBURNGeneratorBase { - MCRCountingDNBURNGenerator() { - super(); + MCRCountingDNBURNGenerator(String namespace) { + super(namespace, ""); } - protected AtomicInteger readCountFromDatabase(String countPattern) { - Pattern regExpPattern = Pattern.compile(countPattern); - Predicate matching = regExpPattern.asPredicate(); - - List list = MCRPIManager.getInstance() - .getList(MCRDNBURN.TYPE, -1, -1); - - // extract the number of the PI - Optional highestNumber = list.stream() - .map(MCRPIRegistrationInfo::getIdentifier) - .filter(matching) - .map(pi -> { - // extract the number of the PI - Matcher matcher = regExpPattern.matcher(pi); - if (matcher.find() && matcher.groupCount() == 1) { - String group = matcher.group(1); - return Integer.parseInt(group, 10); - } else { - return null; - } - }).filter(Objects::nonNull) - .min(Comparator.reverseOrder()) - .map(n -> n + 1); - return new AtomicInteger(highestNumber.orElse(0)); - } - - /** - * Gets the count for a specific pattern and increase the internal counter. If there is no internal counter it will - * look into the Database and detect the highest count with the pattern. - * - * @param pattern a reg exp pattern which will be used to detect the highest count. The first group is the count. - * e.G. [0-9]+-mods-2017-([0-9][0-9][0-9][0-9])-[0-9] will match 31-mods-2017-0003-3 and the returned - * count will be 4 (3+1). - * @return the next count - */ - public final synchronized int getCount(String pattern) { - AtomicInteger count = PATTERN_COUNT_MAP - .computeIfAbsent(pattern, this::readCountFromDatabase); - - return count.getAndIncrement(); - } } diff --git a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCountingDNBURNGeneratorBase.java b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCountingDNBURNGeneratorBase.java new file mode 100644 index 0000000000..63acce463e --- /dev/null +++ b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCountingDNBURNGeneratorBase.java @@ -0,0 +1,57 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.urn; + +import static org.mycore.pi.util.MCRPIGeneratorUtils.readCountFromDatabase; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A Generator which helps to generate a URN with a counter inside. + */ +public abstract class MCRCountingDNBURNGeneratorBase extends MCRDNBURNGeneratorBase { + + private static final Map PATTERN_COUNT_MAP = new HashMap<>(); + + MCRCountingDNBURNGeneratorBase(String namespace, String delimiter) { + super(namespace, delimiter); + } + + MCRCountingDNBURNGeneratorBase(String namespace) { + super(namespace, ""); + } + + /** + * Gets the count for a specific pattern and increase the internal counter. If there is no internal counter it will + * look into the Database and detect the highest count with the pattern. + * + * @param pattern a regex pattern which will be used to detect the highest count. The first group is the count. + * e.G. [0-9]+-mods-2017-([0-9][0-9][0-9][0-9])-[0-9] will match 31-mods-2017-0003-3 and the returned + * count will be 4 (3+1). + * @return the next count + */ + public final synchronized int getCount(String pattern) { + return PATTERN_COUNT_MAP + .computeIfAbsent(pattern, p -> readCountFromDatabase(MCRDNBURN.TYPE, p)) + .getAndIncrement(); + } + +} diff --git a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCreateDateDNBURNGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCreateDateDNBURNGenerator.java new file mode 100644 index 0000000000..7ef3c72e29 --- /dev/null +++ b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCreateDateDNBURNGenerator.java @@ -0,0 +1,147 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.urn; + +import static org.mycore.pi.util.MCRPIGeneratorUtils.formatCount; +import static org.mycore.pi.util.MCRPIGeneratorUtils.getCountPattern; +import static org.mycore.pi.util.MCRPIGeneratorUtils.getCreateDate; +import static org.mycore.pi.util.MCRPIGeneratorUtils.readCountFromDatabase; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRInstance; +import org.mycore.common.config.annotation.MCRProperty; +import org.mycore.common.date.MCRDateFormatter; +import org.mycore.common.date.MCRISO8601DateFormatter; +import org.mycore.datamodel.metadata.MCRBase; +import org.mycore.pi.MCRGenericPIGenerator; +import org.mycore.pi.MCRPIGenerator; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; + +/** + * {@link MCRCreateDateDNBURNGenerator} is a {@link MCRPIGenerator} for {@link MCRDNBURN} identifiers + * that generates identifiers using a given prefix and the current date and a per-date counter for the suffix. + *

    + * The following configuration options are available: + *

      + *
    • The property suffix {@link MCRGenericPIGenerator#DATE_FORMATTER_KEY} can be used to + * specify the date formatter to be used (optional, defaults to {@link MCRISO8601DateFormatter} with format + * {@link MCRCreateDateDNBURNGenerator#DEFAULT_DATE_FORMAT} and locale + * {@link MCRCreateDateDNBURNGenerator#DEFAULT_DATE_LOCALE}). + *
    • The property suffix {@link MCRCreateDateDNBURNGenerator#NAMESPACE_KEY} can be used to + * specify the namespace. + *
    • The property suffix {@link MCRCreateDateDNBURNGenerator#DELIMITER_KEY} can be used to + * specify a delimiter to be placed before and after the NISS (optional, defaults to the empty string). + *
    • The property suffix {@link MCRCreateDateDNBURNGenerator#COUNT_PRECISION_KEY} can be used to + * specify number of digits to be used for the count (optional, defaults to -1, + * which uses the natural number of digits). + *
    + * Example: + *
    
    + * [...].Class=org.mycore.pi.urn.MCRCreateDateDNBURNGenerator
    + * [...].DateFormatter.Class=org.mycore.common.date.MCRSimpleDateFormatter
    + * [...].DateFormatter.Format=yyyy-MM-dd
    + * [...].Namespace=urn:nbn:de:gbv:xyz
    + * [...].Delimiter=-
    + * [...].CountPrecision=6
    + * 
    + */ +@MCRConfigurationProxy(proxyClass = MCRCreateDateDNBURNGenerator.Factory.class) +public class MCRCreateDateDNBURNGenerator extends MCRDNBURNGeneratorBase { + + public static final String DEFAULT_DATE_FORMAT = "yyyyMMdd-HHmmss"; + + public static final Locale DEFAULT_DATE_LOCALE = Locale.ENGLISH; + + public static final String DATE_FORMATTER_KEY = "Formatter"; + + public static final String NAMESPACE_KEY = "Namespace"; + + public static final String DELIMITER_KEY = "Delimiter"; + + public static final String COUNT_PRECISION_KEY = "CountPrecision"; + + private static final Map PATTERN_COUNT_MAP = new HashMap<>(); + + private final MCRDateFormatter dateFormatter; + + private final int countPrecision; + + private final String countPattern; + + public MCRCreateDateDNBURNGenerator(MCRDateFormatter dateFormatter, String namespace, + String delimiter, int countPrecision) { + super(namespace, delimiter); + this.dateFormatter = Objects.requireNonNull(dateFormatter, "Date formatter must not be null"); + this.countPrecision = countPrecision; + this.countPattern = getCountPattern(countPrecision); + } + + @Override + protected String buildNISS(MCRBase base, String additional) throws MCRPersistentIdentifierException { + + String prefixWithDate = dateFormatter.format(getCreateDate(base)) + "-"; + int count = getCount(Pattern.quote(namespace() + delimiter() + prefixWithDate) + + countPattern + Pattern.quote(delimiter()) + "[0-9]"); + + return prefixWithDate + formatCount(count, countPrecision); + + } + + private synchronized int getCount(final String pattern) { + return PATTERN_COUNT_MAP + .computeIfAbsent(pattern, p -> readCountFromDatabase(MCRDNBURN.TYPE, p)) + .getAndIncrement(); + } + + public static class Factory implements Supplier { + + @MCRInstance(name = DATE_FORMATTER_KEY, valueClass = MCRDateFormatter.class, required = false) + public MCRDateFormatter dateFormatter; + + @MCRProperty(name = NAMESPACE_KEY) + public String namespace; + + @MCRProperty(name = DELIMITER_KEY, required = false) + public String delimiter = ""; + + @MCRProperty(name = COUNT_PRECISION_KEY, required = false) + public String countPrecision = "-1"; + + @Override + public MCRCreateDateDNBURNGenerator get() { + return new MCRCreateDateDNBURNGenerator(getDateFormatter(), namespace, delimiter, + Integer.parseInt(countPrecision)); + } + + private MCRDateFormatter getDateFormatter() { + return dateFormatter != null ? dateFormatter + : new MCRISO8601DateFormatter(DEFAULT_DATE_FORMAT, DEFAULT_DATE_LOCALE); + } + + } + +} diff --git a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCurrentDateDNBURNGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCurrentDateDNBURNGenerator.java new file mode 100644 index 0000000000..67ddb819ff --- /dev/null +++ b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRCurrentDateDNBURNGenerator.java @@ -0,0 +1,117 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.urn; + +import java.util.Date; +import java.util.Objects; +import java.util.function.Supplier; + +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRInstance; +import org.mycore.common.config.annotation.MCRProperty; +import org.mycore.common.date.MCRDateFormatter; +import org.mycore.common.date.MCRFLDateScrambler; +import org.mycore.datamodel.metadata.MCRBase; +import org.mycore.pi.MCRPIGenerator; + +/** + * {@link MCRCurrentDateDNBURNGenerator} is a {@link MCRPIGenerator} for {@link MCRDNBURN} identifiers + * that generates identifiers using a given namespace and the current date (in seconds) value as the NISS. + *

    + * Only one suffix per second will be generated. + *

    + * The following configuration options are available: + *

      + *
    • The property suffix {@link MCRCurrentDateDNBURNGenerator#DATE_FORMATTER_KEY} can be used to + * specify the date formatter to be used (optional, defaults to {@link MCRFLDateScrambler}). + *
    • The property suffix {@link MCRCurrentDateDNBURNGenerator#NAMESPACE_KEY} can be used to + * specify the namespace. + *
    • The property suffix {@link MCRCurrentDateDNBURNGenerator#DELIMITER_KEY} can be used to + * specify a delimiter to be placed before and after the NISS (optional, defaults to the empty string). + *
    + * Example: + *
    
    + * [...].Class=org.mycore.pi.urn.MCRCurrentDateDNBURNGenerator
    + * [...].DateFormatter.Class=org.mycore.common.date.MCRSimpleDateFormatter
    + * [...].DateFormatter.Format=yyyy-MM-dd
    + * [...].Namespace=urn:nbn:de:gbv:xyz
    + * [...].Delimiter=-
    + * 
    + */ +@MCRConfigurationProxy(proxyClass = MCRCurrentDateDNBURNGenerator.Factory.class) +public class MCRCurrentDateDNBURNGenerator extends MCRDNBURNGeneratorBase { + + public static final String DATE_FORMATTER_KEY = "DateFormatter"; + + public static final String NAMESPACE_KEY = "Namespace"; + + public static final String DELIMITER_KEY = "Delimiter"; + + private final MCRDateFormatter dateFormatter; + + private String lastNIss; + + public MCRCurrentDateDNBURNGenerator(MCRDateFormatter dateFormatter, String namespace, String delimiter) { + super(namespace, delimiter); + this.dateFormatter = Objects.requireNonNull(dateFormatter, "Date formatter must not be null"); + } + + @Override + protected synchronized String buildNISS(MCRBase base, String additional) { + + Date date = new Date((System.currentTimeMillis() / 1000) * 1000); + String niss = dateFormatter.format(date); + + if (niss.equals(lastNIss)) { + try { + Thread.sleep(500); + } catch (InterruptedException ignored) { + } + return buildNISS(base, additional); + } + + lastNIss = niss; + + return niss; + + } + + public static class Factory implements Supplier { + + @MCRInstance(name = DATE_FORMATTER_KEY, valueClass = MCRDateFormatter.class, required = false) + public MCRDateFormatter formatter; + + @MCRProperty(name = NAMESPACE_KEY) + public String namespace; + + @MCRProperty(name = DELIMITER_KEY, required = false) + public String delimiter = ""; + + @Override + public MCRCurrentDateDNBURNGenerator get() { + return new MCRCurrentDateDNBURNGenerator(getFormatter(), namespace, delimiter); + } + + private MCRDateFormatter getFormatter() { + return formatter != null ? formatter : new MCRFLDateScrambler(); + } + + } + +} diff --git a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRDNBURNGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRDNBURNGenerator.java index 9fcb994a21..78b397e3c4 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRDNBURNGenerator.java +++ b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRDNBURNGenerator.java @@ -18,46 +18,14 @@ package org.mycore.pi.urn; -import java.util.Objects; - -import org.mycore.datamodel.metadata.MCRBase; -import org.mycore.datamodel.metadata.MCRObjectID; -import org.mycore.pi.MCRPIGenerator; - -import jakarta.validation.constraints.NotNull; - -public abstract class MCRDNBURNGenerator extends MCRPIGenerator { - - private static final String URN_NBN_DE = "urn:nbn:de:"; - - protected abstract String buildNISS(MCRObjectID mcrID, String additional); - - /** - * Allows the generation of a URN with a specific Namespace - * - * @param namespace the namespace of the generated URN - * @param mcrID the mycore object for which the identifier is generated - * @param additional additional information dedicated to the object like a mcrpath - * @return a unique persistence identifier - */ - protected MCRDNBURN generate(@NotNull String namespace, MCRObjectID mcrID, String additional) { - Objects.requireNonNull(namespace, "Namespace for an URN must not be null!"); - return new MCRDNBURN(namespace, buildNISS(mcrID, additional)); - } - - @Override - public MCRDNBURN generate(MCRBase mcrObj, String additional) { - return generate(getNamespace(), mcrObj.getId(), additional); - } - - public String getNamespace() { - String namespace = getProperties().get("Namespace").trim(); - - if (namespace.startsWith(URN_NBN_DE)) { - namespace = namespace.substring(URN_NBN_DE.length()); - } +/** + * @deprecated Use {@link MCRDNBURNGeneratorBase} instead. + */ +@Deprecated(forRemoval = true) +public abstract class MCRDNBURNGenerator extends MCRDNBURNGeneratorBase { - return namespace; + public MCRDNBURNGenerator(String namespace) { + super(namespace, ""); } } diff --git a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRDNBURNGeneratorBase.java b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRDNBURNGeneratorBase.java new file mode 100644 index 0000000000..76cb6e4c40 --- /dev/null +++ b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRDNBURNGeneratorBase.java @@ -0,0 +1,64 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.urn; + +import java.util.Objects; + +import org.mycore.datamodel.metadata.MCRBase; +import org.mycore.pi.MCRPIGenerator; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; + +public abstract class MCRDNBURNGeneratorBase implements MCRPIGenerator { + + private static final String URN_NBN_DE = "urn:nbn:de:"; + + private final String namespace; + + private final String delimiter; + + public MCRDNBURNGeneratorBase(String namespace, String delimiter) { + this.namespace = checkNamespace(Objects.requireNonNull(namespace, "Namespace must not be null")); + this.delimiter = Objects.requireNonNull(delimiter, "Delimiter must not be null"); + } + + private static String checkNamespace(String namespace) { + if (namespace.startsWith(URN_NBN_DE)) { + namespace = namespace.substring(URN_NBN_DE.length()); + } + return namespace; + } + + protected final String namespace() { + return namespace; + } + + protected final String delimiter() { + return delimiter; + } + + @Override + public MCRDNBURN generate(MCRBase base, String additional) + throws MCRPersistentIdentifierException { + return new MCRDNBURN(namespace, delimiter + buildNISS(base, additional) + delimiter); + } + + protected abstract String buildNISS(MCRBase base, String additional) + throws MCRPersistentIdentifierException; + +} diff --git a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRFLURNGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRFLURNGenerator.java index 8c0e9669e1..df02776798 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRFLURNGenerator.java +++ b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRFLURNGenerator.java @@ -18,51 +18,36 @@ package org.mycore.pi.urn; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.Locale; -import java.util.TimeZone; +import java.util.function.Supplier; -import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; +import org.mycore.common.date.MCRFLDateScrambler; /** - * Builds a new, unique NISS based on the current date and time expressed - * in seconds. The resulting NISS is non-speaking, but unique and somewhat - * optimized for the nbn:de checksum algorithm. Only one NISS per second - * will be generated. - * - * @author Frank Lützenkirchen + * @deprecated Use {@link MCRCurrentDateDNBURNGenerator} instead. */ -public class MCRFLURNGenerator extends MCRDNBURNGenerator { - private String last; +@Deprecated(forRemoval = true) +@MCRConfigurationProxy(proxyClass = MCRFLURNGenerator.Factory.class) +public class MCRFLURNGenerator extends MCRCurrentDateDNBURNGenerator { - @Override - protected synchronized String buildNISS(MCRObjectID mcrID, String additional) { - Calendar now = new GregorianCalendar(TimeZone.getTimeZone("GMT+01:00"), Locale.ENGLISH); - int yyy = 2268 - now.get(Calendar.YEAR); - int ddd = 500 - now.get(Calendar.DAY_OF_YEAR); - int hh = now.get(Calendar.HOUR_OF_DAY); - int mm = now.get(Calendar.MINUTE); - int ss = now.get(Calendar.SECOND); - int sss = 99_999 - (hh * 3600 + mm * 60 + ss); + public MCRFLURNGenerator(String namespace, String delimiter) { + super(new MCRFLDateScrambler(), namespace, delimiter); + } - String ddddd = String.valueOf(yyy * 366 + ddd); + public static class Factory implements Supplier { - String niss = String.valueOf(ddddd.charAt(4)) + ddddd.charAt(2) + ddddd.charAt(1) + ddddd.charAt(3) - + ddddd.charAt(0) - + sss; + @MCRProperty(name = NAMESPACE_KEY) + public String namespace; - if (niss.equals(last)) { - try { - Thread.sleep(500); - } catch (InterruptedException ignored) { - } + @MCRProperty(name = DELIMITER_KEY, required = false) + public String delimiter = ""; - return buildNISS(mcrID, additional); - } else { - last = niss; - return niss; + @Override + public MCRFLURNGenerator get() { + return new MCRFLURNGenerator(namespace, delimiter); } + } } diff --git a/mycore-pi/src/main/java/org/mycore/pi/urn/MCROtherPIDNBURNGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/urn/MCROtherPIDNBURNGenerator.java new file mode 100644 index 0000000000..a039c0b177 --- /dev/null +++ b/mycore-pi/src/main/java/org/mycore/pi/urn/MCROtherPIDNBURNGenerator.java @@ -0,0 +1,108 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.urn; + +import java.util.Objects; +import java.util.function.Supplier; + +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; +import org.mycore.datamodel.metadata.MCRBase; +import org.mycore.pi.MCRPIGenerator; +import org.mycore.pi.util.MCROtherPIValueExtractor; + +/** + * {@link MCROtherPIDNBURNGenerator} is a {@link MCRPIGenerator} for {@link MCRDNBURN} identifiers + * that generates identifiers using a given namespace and a value extracted from another PI + * that is already assigned as the NISS. + *

    + * The following configuration options are available: + *

      + *
    • The property suffix {@link MCROtherPIDNBURNGenerator#NAMESPACE_KEY} can be used to + * specify the namespace. + *
    • The property suffix {@link MCROtherPIDNBURNGenerator#DELIMITER_KEY} can be used to + * specify a delimiter to be placed before and after the NISS (optional, defaults to the empty string). + *
    • The property suffix {@link MCROtherPIDNBURNGenerator#TYPE_KEY} can be used to + * specify the type of the assigned PI. + *
    • The property suffix {@link MCROtherPIDNBURNGenerator#SERVICE_KEY} can be used to + * specify the service of the assigned PI. + *
    • The property suffix {@link MCROtherPIDNBURNGenerator#PATTERN_KEY} can be used to + * specify the pattern (must contain a single capture group). + *
    + * Example: + *
    
    + * [...].Class=org.mycore.pi.urn.MCROtherPIDNBURNGenerator
    + * [...].Namespace=urn:nbn:de:gbv:xyz
    + * [...].Delimiter=-
    + * [...].Type=doi
    + * [...].Service=Datacite
    + * [...].Pattern=10.1234/(.+)
    + * 
    + */ +@MCRConfigurationProxy(proxyClass = MCROtherPIDNBURNGenerator.Factory.class) +public class MCROtherPIDNBURNGenerator extends MCRDNBURNGeneratorBase { + + public static final String TYPE_KEY = "Type"; + + public static final String SERVICE_KEY = "Service"; + + public static final String PATTERN_KEY = "Pattern"; + + public static final String NAMESPACE_KEY = "Namespace"; + + public static final String DELIMITER_KEY = "Delimiter"; + + private final MCROtherPIValueExtractor extractor; + + public MCROtherPIDNBURNGenerator(String namespace, String delimiter, MCROtherPIValueExtractor extractor) { + super(namespace, delimiter); + this.extractor = Objects.requireNonNull(extractor, "Extractor must not be null"); + } + + @Override + protected String buildNISS(MCRBase base, String additional) { + return extractor.extractValue(base.getId()); + } + + public static class Factory implements Supplier { + + @MCRProperty(name = NAMESPACE_KEY) + public String namespace; + + @MCRProperty(name = DELIMITER_KEY, required = false) + public String delimiter = ""; + + @MCRProperty(name = TYPE_KEY) + public String type; + + @MCRProperty(name = SERVICE_KEY) + public String service; + + @MCRProperty(name = PATTERN_KEY) + public String pattern; + + @Override + public MCROtherPIDNBURNGenerator get() { + MCROtherPIValueExtractor extractor = new MCROtherPIValueExtractor(type, service, pattern); + return new MCROtherPIDNBURNGenerator(namespace, delimiter, extractor); + } + + } + +} diff --git a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRUUIDDNBURNGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRUUIDDNBURNGenerator.java new file mode 100644 index 0000000000..f8ea12ef8e --- /dev/null +++ b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRUUIDDNBURNGenerator.java @@ -0,0 +1,78 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.urn; + +import java.util.UUID; +import java.util.function.Supplier; + +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; +import org.mycore.datamodel.metadata.MCRBase; +import org.mycore.pi.MCRPIGenerator; + +/** + * {@link MCRUUIDDNBURNGenerator} is a {@link MCRPIGenerator} for {@link MCRDNBURN} identifiers + * that generates identifiers using a given namespace and a {@link UUID} as the NISS. + *

    + * The following configuration options are available: + *

      + *
    • The property suffix {@link MCRUUIDDNBURNGenerator#NAMESPACE_KEY} can be used to + * specify the namespace. + *
    • The property suffix {@link MCRUUIDDNBURNGenerator#DELIMITER_KEY} can be used to + * specify a delimiter to be placed before and after the NISS (optional, defaults to the empty string). + *
    + * Example: + *
    
    + * [...].Class=org.mycore.pi.urn.MCRUUIDDNBURNGenerator
    + * [...].Namespace=urn:nbn:de:gbv:xyz
    + * [...].Delimiter=-
    + * 
    + */ +@MCRConfigurationProxy(proxyClass = MCRUUIDDNBURNGenerator.Factory.class) +public class MCRUUIDDNBURNGenerator extends MCRDNBURNGeneratorBase { + + public static final String NAMESPACE_KEY = "Namespace"; + + public static final String DELIMITER_KEY = "Delimiter"; + + public MCRUUIDDNBURNGenerator(String namespace, String delimiter) { + super(namespace, delimiter); + } + + @Override + protected String buildNISS(MCRBase base, String additional) { + return UUID.randomUUID().toString(); + } + + public static class Factory implements Supplier { + + @MCRProperty(name = NAMESPACE_KEY) + public String namespace; + + @MCRProperty(name = DELIMITER_KEY, required = false) + public String delimiter = ""; + + @Override + public MCRUUIDDNBURNGenerator get() { + return new MCRUUIDDNBURNGenerator(namespace, delimiter); + } + + } + +} diff --git a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRUUIDURNGenerator.java b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRUUIDURNGenerator.java index 12c45eef62..ce40223dcb 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRUUIDURNGenerator.java +++ b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRUUIDURNGenerator.java @@ -18,27 +18,35 @@ package org.mycore.pi.urn; -import java.util.UUID; +import java.util.function.Supplier; -import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.common.config.annotation.MCRConfigurationProxy; +import org.mycore.common.config.annotation.MCRProperty; /** - * Builds a new, unique NISS using Java implementation of the UUID - * specification. java.util.UUID creates 'only' version 4 UUIDs. - * Version 4 UUIDs are generated from a large random number and do - * not include the MAC address. - *

    - * UUID = 8*HEX "-" 4*HEX "-" 4*HEX "-" 4*HEX "-" 12*HEX - * Example One: 067e6162-3b6f-4ae2-a171-2470b63dff00 - * Example Two: 54947df8-0e9e-4471-a2f9-9af509fb5889 - * - * @author Kathleen Neumann (kkrebs) - * @author Sebastian Hofmann + * @deprecated Use {@link MCRUUIDDNBURNGenerator} instead. */ -public class MCRUUIDURNGenerator extends MCRDNBURNGenerator { +@Deprecated(forRemoval = true) +@MCRConfigurationProxy(proxyClass = MCRUUIDURNGenerator.Factory.class) +public class MCRUUIDURNGenerator extends MCRUUIDDNBURNGenerator { + + public MCRUUIDURNGenerator(String namespace, String delimiter) { + super(namespace, delimiter); + } + + public static class Factory implements Supplier { + + @MCRProperty(name = NAMESPACE_KEY) + public String namespace; + + @MCRProperty(name = DELIMITER_KEY, required = false) + public String delimiter = ""; + + @Override + public MCRUUIDURNGenerator get() { + return new MCRUUIDURNGenerator(namespace, delimiter); + } - @Override - protected String buildNISS(MCRObjectID mcrID, String additional) { - return UUID.randomUUID().toString(); } + } diff --git a/mycore-pi/src/main/java/org/mycore/pi/util/MCROtherPIValueExtractor.java b/mycore-pi/src/main/java/org/mycore/pi/util/MCROtherPIValueExtractor.java new file mode 100644 index 0000000000..555a156196 --- /dev/null +++ b/mycore-pi/src/main/java/org/mycore/pi/util/MCROtherPIValueExtractor.java @@ -0,0 +1,90 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.util; + +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.mycore.common.MCRException; +import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.pi.MCRPIManager; +import org.mycore.pi.MCRPIRegistrationInfo; + +public final class MCROtherPIValueExtractor { + + private final String type; + + private final String service; + + private final Pattern pattern; + + public MCROtherPIValueExtractor(String type, String service, String pattern) { + this.type = Objects.requireNonNull(type); + this.service = Objects.requireNonNull(service); + this.pattern = checkPattern(Pattern.compile(Objects.requireNonNull(pattern))); + } + + private static Pattern checkPattern(Pattern pattern) { + + Matcher matcher = pattern.matcher(""); + + boolean hasExactlyOneCaptureGroup = matcher.groupCount() == 1; + if (!hasExactlyOneCaptureGroup) { + throw new IllegalArgumentException("Pattern doesn't have exactly one capture group: " + + pattern.pattern()); + } + + return pattern; + + } + + public String extractValue(MCRObjectID objectID) { + + List createdIdentifiers = MCRPIManager.getInstance() + .getCreatedIdentifiers(objectID, type, service); + + if (createdIdentifiers.isEmpty()) { + throw new MCRException("No identifier found object " + objectID + ", type " + type + + " and service " + service); + } + + String identifier = createdIdentifiers.getFirst().getIdentifier(); + Matcher matcher = pattern.matcher(identifier); + + if (!matcher.find()) { + throw new MCRException("Identifier " + identifier + " found for object " + objectID + + ", type " + type + " and service " + service + " doesn't match pattern " + + pattern.pattern()); + } + + String value = matcher.group(1); + + if (value.isEmpty()) { + throw new MCRException("Identifier " + identifier + " found for object " + objectID + + ", type " + type + " and service " + service + " contains empty value for pattern " + + pattern.pattern()); + } + + return value; + + } + +} diff --git a/mycore-pi/src/main/java/org/mycore/pi/util/MCRPIGeneratorUtils.java b/mycore-pi/src/main/java/org/mycore/pi/util/MCRPIGeneratorUtils.java new file mode 100644 index 0000000000..1246d402fc --- /dev/null +++ b/mycore-pi/src/main/java/org/mycore/pi/util/MCRPIGeneratorUtils.java @@ -0,0 +1,115 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.util; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.mycore.datamodel.metadata.MCRBase; +import org.mycore.datamodel.metadata.MCRObjectService; +import org.mycore.pi.MCRPIGenerator; +import org.mycore.pi.MCRPIManager; +import org.mycore.pi.MCRPIRegistrationInfo; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; + +/** + * Utility class providing functions commonly used by implementations of {@link MCRPIGenerator}. + */ +public final class MCRPIGeneratorUtils { + + private MCRPIGeneratorUtils() { + } + + public static AtomicInteger readCountFromDatabase(String type, String countPattern) { + + Pattern pattern = Pattern.compile(countPattern); + Predicate matching = pattern.asPredicate(); + + List list = MCRPIManager.getInstance().getList(type, -1, -1); + + // extract the number of the PI + Optional highestNumber = list.stream() + .map(MCRPIRegistrationInfo::getIdentifier) + .filter(matching) + .map(pi -> { + // extract the number of the PI + Matcher matcher = pattern.matcher(pi); + if (matcher.find() && matcher.groupCount() == 1) { + String group = matcher.group(1); + return Integer.parseInt(group, 10); + } else { + return null; + } + }).filter(Objects::nonNull) + .max(Comparator.naturalOrder()) + .map(n -> n + 1); + + return new AtomicInteger(highestNumber.orElse(0)); + + } + + public static String getCountPattern(int countPrecision) { + String countPattern; + if (countPrecision == -1) { + countPattern = "([0-9]+)"; + } else { + countPattern = "(" + "[0-9]".repeat(countPrecision) + ")"; + } + return countPattern; + } + + public static String formatCount(int count, int counterPrecision) + throws MCRPersistentIdentifierException { + + if (counterPrecision == -1) { + return String.valueOf(count); + } + + int actualLength = String.valueOf(Math.abs(count)).length(); + if (actualLength > counterPrecision) { + throw new MCRPersistentIdentifierException( + "Count " + count + " (" + actualLength + " digits) exceeds counter precision of " + + counterPrecision + " digits."); + } + + DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ROOT); + DecimalFormat decimalFormat = new DecimalFormat("0".repeat(counterPrecision), symbols); + return decimalFormat.format(count); + + } + + public static Date getCreateDate(MCRBase base) throws MCRPersistentIdentifierException { + Date createDate = base.getService().getDate(MCRObjectService.DATE_TYPE_CREATEDATE); + if (createDate == null) { + throw new MCRPersistentIdentifierException("Object " + base.getId() + " doesn't have a create date!"); + } + return createDate; + } + +} diff --git a/mycore-pi/src/main/resources/components/pi/config/deprecated.properties b/mycore-pi/src/main/resources/components/pi/config/deprecated.properties index c8444959e2..bb5c432e54 100644 --- a/mycore-pi/src/main/resources/components/pi/config/deprecated.properties +++ b/mycore-pi/src/main/resources/components/pi/config/deprecated.properties @@ -11,3 +11,5 @@ MCR.PI.Generator.(\S+)=MCR.PI.Generator.$1.Class MCR.PI.MetadataService.(\S+)=MCR.PI.MetadataService.$1.Class MCR.URIResolver.ModuleResolver.pi=MCR.URIResolver.ModuleResolver.pi.Class + +MCR.PI.Generator.TypeYearCountURN.DateFormat=(MCR.PI.Generator.TypeYearCountURN.DateFormatter.Class=org.mycore.common.date.MCRSimpleDateFormatter and MCR.PI.Generator.TypeYearCountURN.DateFormatter.Format) diff --git a/mycore-pi/src/main/resources/components/pi/config/mycore.properties b/mycore-pi/src/main/resources/components/pi/config/mycore.properties index d5b190bb31..2209d2ee14 100644 --- a/mycore-pi/src/main/resources/components/pi/config/mycore.properties +++ b/mycore-pi/src/main/resources/components/pi/config/mycore.properties @@ -49,13 +49,15 @@ MCR.PI.Default.Service.LinkedUpdatedTriggerUpdate=true # Generators MCR.PI.Generator.UUIDDOI.Class=org.mycore.pi.doi.MCRUUIDDOIGenerator +MCR.PI.Generator.DateDOI.Class=org.mycore.pi.doi.MCRCreateDateDOIGenerator MCR.PI.Generator.MapObjectIDDOI.Class=org.mycore.pi.doi.MCRMapObjectIDDOIGenerator #MCR.PI.Generator.IDPURLGenerator.Class=org.mycore.pi.purl.MCRIDPURLGenerator #MCR.PI.Generator.IDPURLGenerator.BaseURLTemplate=http://purl.meineUrl.de/$ID #MCR.PI.Generator.TypeYearCountURN.Class=org.mycore.pi.MCRGenericPIGenerator #MCR.PI.Generator.TypeYearCountURN.GeneralPattern=urn:nbn:de:gbv:$ObjectType-$ObjectDate-$Count- -#MCR.PI.Generator.TypeYearCountURN.DateFormat=yyyy +#MCR.PI.Generator.TypeYearCountURN.DateFormatter.Class=org.mycore.common.date.MCRSimpleDateFormatter +#MCR.PI.Generator.TypeYearCountURN.DateFormatter.Format=yyyy #MCR.PI.Generator.TypeYearCountURN.CountPrecision=5 #MCR.PI.Generator.TypeYearCountURN.Type=dnbUrn diff --git a/mycore-pi/src/test/java/org/mycore/pi/MCRGenericPIGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/MCRGenericPIGeneratorTest.java index bc25d26db5..dd31f5a7fb 100644 --- a/mycore-pi/src/test/java/org/mycore/pi/MCRGenericPIGeneratorTest.java +++ b/mycore-pi/src/test/java/org/mycore/pi/MCRGenericPIGeneratorTest.java @@ -19,19 +19,26 @@ package org.mycore.pi; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mycore.pi.MCRGenericPIGenerator.DEFAULT_DATE_FORMAT; +import static org.mycore.pi.MCRGenericPIGenerator.DEFAULT_DATE_LOCALE; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Locale; +import java.util.List; +import java.util.Map; import org.jdom2.Element; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mycore.common.MCRTestConfiguration; import org.mycore.common.MCRTestProperty; +import org.mycore.common.date.MCRMockDateFormatter; +import org.mycore.common.date.MCRSimpleDateFormatter; import org.mycore.datamodel.metadata.MCRObject; import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.pi.doi.MCRDigitalObjectIdentifier; import org.mycore.pi.exceptions.MCRPersistentIdentifierException; +import org.mycore.pi.urn.MCRDNBURN; import org.mycore.test.MCRJPAExtension; import org.mycore.test.MCRMetadataExtension; import org.mycore.test.MyCoReTest; @@ -44,33 +51,73 @@ }) public class MCRGenericPIGeneratorTest { - public static final int CURRENT_YEAR = Calendar.getInstance().get(Calendar.YEAR); - @Test public void testGenerate() throws MCRPersistentIdentifierException { - final MCRGenericPIGenerator generator = new MCRGenericPIGenerator( - "urn:nbn:de:gbv:$CurrentDate-$1-$2-$ObjectType-$ObjectProject-$ObjectNumber-$Count-", - new SimpleDateFormat("yyyy", Locale.ROOT), null, null, 3, - "dnbUrn", "/mycoreobject/metadata/test1/test2/text()", "/mycoreobject/metadata/test1/test3/text()"); - - //generator.init(MCRPIService.GENERATOR_CONFIG_PREFIX + "test1"); - - MCRObjectID testID = MCRObjectID.getInstance("my_test_00000001"); - MCRObject mcrObject = new MCRObject(); - mcrObject.setSchema("test"); - mcrObject.setId(testID); - final Element metadata = new Element("metadata"); - final Element testElement = new Element("test1"); - metadata.addContent(testElement); + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + Element testElement = new Element("test1"); testElement.setAttribute("class", "MCRMetaXML"); testElement.addContent(new Element("test2").setText("result1")); testElement.addContent(new Element("test3").setText("result2")); - mcrObject.getMetadata().setFromDOM(metadata); - final String pi1 = generator.generate(mcrObject, "").asString(); - final String pi2 = generator.generate(mcrObject, "").asString(); - assertEquals("urn:nbn:de:gbv:" + CURRENT_YEAR + "-result1-result2-test-my-00000001-000-", pi1.substring(0, pi1.length() - 1)); - assertEquals("urn:nbn:de:gbv:" + CURRENT_YEAR + "-result1-result2-test-my-00000001-001-", pi2.substring(0, pi2.length() - 1)); + Element metadata = new Element("metadata"); + metadata.addContent(testElement); + + object.getMetadata().setFromDOM(metadata); + + MCRMockDateFormatter formatter = new MCRMockDateFormatter(); + MCRGenericPIGenerator generator = new MCRGenericPIGenerator( + "urn:nbn:de:gbv:xyz:$CurrentDate-$1-$2-$ObjectType-$ObjectProject-$ObjectNumber-$Count-", + formatter, + Map.of("my", "MY"), + Map.of("test", "TEST"), + 3, + MCRDNBURN.TYPE, + List.of("/mycoreobject/metadata/test1/test2/text()", "/mycoreobject/metadata/test1/test3/text()")); + + String pi = generator.generate(object, "").asString(); + + assertEquals("urn:nbn:de:gbv:xyz:" + formatter.lastFormattedDate() + "-result1-result2-TEST-MY-00000123-000-", + pi.substring(0, pi.length() - 1)); + + } + + @Test + public void generateMultiple() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRSimpleDateFormatter formatter = new MCRSimpleDateFormatter(DEFAULT_DATE_FORMAT, DEFAULT_DATE_LOCALE); + MCRGenericPIGenerator generator = new MCRGenericPIGenerator( + "10.1234/$ObjectType-$Count", + formatter, + Map.of(), + Map.of(), + -1, + MCRDigitalObjectIdentifier.TYPE, + List.of()); + + String doi1 = generator.generate(object, "").asString(); + String doi2 = generator.generate(object, "").asString(); + String doi3 = generator.generate(object, "").asString(); + + assertNotEquals(doi1, doi2); + assertNotEquals(doi2, doi3); + assertNotEquals(doi3, doi1); + + assertTrue(doi1.startsWith("10.1234/test-")); + assertTrue(doi2.startsWith("10.1234/test-")); + assertTrue(doi3.startsWith("10.1234/test-")); + + assertTrue(doi1.endsWith("-0")); + assertTrue(doi2.endsWith("-1")); + assertTrue(doi3.endsWith("-2")); + } } diff --git a/mycore-pi/src/test/java/org/mycore/pi/MCRMockIdentifierGenerator.java b/mycore-pi/src/test/java/org/mycore/pi/MCRMockIdentifierGenerator.java index 256b597f52..3d39fd9c93 100644 --- a/mycore-pi/src/test/java/org/mycore/pi/MCRMockIdentifierGenerator.java +++ b/mycore-pi/src/test/java/org/mycore/pi/MCRMockIdentifierGenerator.java @@ -18,21 +18,15 @@ package org.mycore.pi; -import static org.junit.jupiter.api.Assertions.assertEquals; - import org.mycore.datamodel.metadata.MCRBase; -public class MCRMockIdentifierGenerator extends MCRPIGenerator { - - public static final String TEST_PROPERTY = "mockProperty"; - - public static final String TEST_PROPERTY_VALUE = "mockPropertyValue"; +public class MCRMockIdentifierGenerator implements MCRPIGenerator { @Override - public MCRMockIdentifier generate(MCRBase mcrBase, String additional) { - assertEquals(TEST_PROPERTY_VALUE, getProperties().get(TEST_PROPERTY), "Test properties should be set!"); - - return (MCRMockIdentifier) new MCRMockIdentifierParser() - .parse(MCRMockIdentifier.MOCK_SCHEME + mcrBase.getId() + ":" + additional).get(); + public MCRMockIdentifier generate(MCRBase base, String additional) { + return new MCRMockIdentifierParser() + .parse(MCRMockIdentifier.MOCK_SCHEME + base.getId() + ":" + additional) + .get(); } + } diff --git a/mycore-pi/src/test/java/org/mycore/pi/MCRMockIdentifierParser.java b/mycore-pi/src/test/java/org/mycore/pi/MCRMockIdentifierParser.java index 7dbe51a740..9c63fc752e 100644 --- a/mycore-pi/src/test/java/org/mycore/pi/MCRMockIdentifierParser.java +++ b/mycore-pi/src/test/java/org/mycore/pi/MCRMockIdentifierParser.java @@ -20,10 +20,10 @@ import java.util.Optional; -public class MCRMockIdentifierParser implements MCRPIParser { +public class MCRMockIdentifierParser implements MCRPIParser { @Override - public Optional parse(String identifier) { + public Optional parse(String identifier) { if (!identifier.startsWith(MCRMockIdentifier.MOCK_SCHEME)) { return Optional.empty(); } diff --git a/mycore-pi/src/test/java/org/mycore/pi/MCRPIManagerTest.java b/mycore-pi/src/test/java/org/mycore/pi/MCRPIManagerTest.java index 49330fbdf6..0f33587af1 100644 --- a/mycore-pi/src/test/java/org/mycore/pi/MCRPIManagerTest.java +++ b/mycore-pi/src/test/java/org/mycore/pi/MCRPIManagerTest.java @@ -67,10 +67,6 @@ + MCRMockMetadataService.TEST_PROPERTY, string = MCRMockMetadataService.TEST_PROPERTY_VALUE), @MCRTestProperty(key = "MCR.PI.Generator." + MCRPIManagerTest.MOCK_PID_GENERATOR + ".Class", classNameOf = MCRMockIdentifierGenerator.class), - @MCRTestProperty(key = "MCR.PI.Generator." + MCRPIManagerTest.MOCK_PID_GENERATOR + "." - + MCRMockIdentifierGenerator.TEST_PROPERTY, string = MCRMockIdentifierGenerator.TEST_PROPERTY_VALUE), - @MCRTestProperty(key = "MCR.PI.Generator." + MCRPIManagerTest.MOCK_PID_GENERATOR + ".Namespace", - string = "frontend-"), @MCRTestProperty(key = "MCR.PI.Parsers." + MCRMockIdentifierService.TYPE, classNameOf = MCRMockIdentifierParser.class), @MCRTestProperty(key = "MCR.QueuedJob.activated", string = "true"), diff --git a/mycore-pi/src/test/java/org/mycore/pi/MCRPIUtils.java b/mycore-pi/src/test/java/org/mycore/pi/MCRPIUtils.java index 7985aaa60d..40399b76c8 100644 --- a/mycore-pi/src/test/java/org/mycore/pi/MCRPIUtils.java +++ b/mycore-pi/src/test/java/org/mycore/pi/MCRPIUtils.java @@ -30,8 +30,9 @@ import org.mycore.datamodel.metadata.MCRObject; import org.mycore.datamodel.metadata.MCRObjectID; import org.mycore.pi.backend.MCRPI; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; import org.mycore.pi.urn.MCRDNBURN; -import org.mycore.pi.urn.MCRUUIDURNGenerator; +import org.mycore.pi.urn.MCRUUIDDNBURNGenerator; import org.mycore.pi.urn.rest.MCRDNBURNRestClient; import org.mycore.pi.urn.rest.MCRURNJsonBundle; @@ -44,9 +45,10 @@ public class MCRPIUtils { private static final Logger LOGGER = LogManager.getLogger(); - public static MCRPI generateMCRPI(String fileName, String serviceID) { + public static MCRPI generateMCRPI(String fileName, String serviceID, String namespace) + throws MCRPersistentIdentifierException { MCRObjectID mycoreID = getNextFreeID(); - return new MCRPI(generateURNFor(mycoreID).asString(), MCRDNBURN.TYPE, + return new MCRPI(generateURNFor(mycoreID, namespace).asString(), MCRDNBURN.TYPE, mycoreID.toString(), fileName, serviceID, null); } @@ -54,13 +56,12 @@ public static MCRObjectID getNextFreeID() { return MCRMetadataManager.getMCRObjectIDGenerator().getNextFreeId("MyCoRe_test"); } - private static MCRDNBURN generateURNFor(MCRObjectID mycoreID) { - String testGenerator = "testGenerator"; - MCRUUIDURNGenerator mcruuidurnGenerator = new MCRUUIDURNGenerator(); - mcruuidurnGenerator.init(MCRPIService.GENERATOR_CONFIG_PREFIX + testGenerator); + private static MCRDNBURN generateURNFor(MCRObjectID mycoreID, String namespace) + throws MCRPersistentIdentifierException { + MCRUUIDDNBURNGenerator generator = new MCRUUIDDNBURNGenerator(namespace, ""); MCRObject mcrObject1 = new MCRObject(); mcrObject1.setId(mycoreID); - return mcruuidurnGenerator.generate(mcrObject1, ""); + return generator.generate(mcrObject1, ""); } public static String randomFilename() { diff --git a/mycore-pi/src/test/java/org/mycore/pi/doi/MCRCreateDateDOIGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/doi/MCRCreateDateDOIGeneratorTest.java new file mode 100644 index 0000000000..c4d292de33 --- /dev/null +++ b/mycore-pi/src/test/java/org/mycore/pi/doi/MCRCreateDateDOIGeneratorTest.java @@ -0,0 +1,91 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.doi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mycore.common.MCRTestConfiguration; +import org.mycore.common.MCRTestProperty; +import org.mycore.common.date.MCRDateFormatter; +import org.mycore.common.date.MCRFLDateScrambler; +import org.mycore.common.date.MCRMockDateFormatter; +import org.mycore.datamodel.metadata.MCRObject; +import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; +import org.mycore.test.MCRJPAExtension; +import org.mycore.test.MyCoReTest; + +@MyCoReTest +@ExtendWith({ MCRJPAExtension.class }) +@MCRTestConfiguration(properties = { + @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true"), +}) +public class MCRCreateDateDOIGeneratorTest { + + public static final String PREFIX = "10.1234"; + + @Test + public void generate() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRMockDateFormatter formatter = new MCRMockDateFormatter(); + MCRCreateDateDOIGenerator generator = new MCRCreateDateDOIGenerator(new MCRDOIParser(), formatter, PREFIX, 3); + String doi = generator.generate(object, "").asString(); + + assertTrue(doi.startsWith(PREFIX)); + assertEquals('/', doi.charAt(PREFIX.length())); + + String value = doi.substring(PREFIX.length() + 1); + + assertTrue(value.startsWith(formatter.lastFormattedDate() + "-")); + assertTrue(value.endsWith("-000")); + + } + + @Test + public void generateMultiple() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRDateFormatter formatter = new MCRFLDateScrambler(); + MCRCreateDateDOIGenerator generator = new MCRCreateDateDOIGenerator(new MCRDOIParser(), formatter, PREFIX, -1); + String doi1 = generator.generate(object, "").asString(); + String doi2 = generator.generate(object, "").asString(); + String doi3 = generator.generate(object, "").asString(); + + assertNotEquals(doi1, doi2); + assertNotEquals(doi2, doi3); + assertNotEquals(doi3, doi1); + + assertTrue(doi1.endsWith("-0")); + assertTrue(doi2.endsWith("-1")); + assertTrue(doi3.endsWith("-2")); + + } + +} diff --git a/mycore-pi/src/test/java/org/mycore/pi/doi/MCRCurrentDateDOIGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/doi/MCRCurrentDateDOIGeneratorTest.java new file mode 100644 index 0000000000..06a45699ae --- /dev/null +++ b/mycore-pi/src/test/java/org/mycore/pi/doi/MCRCurrentDateDOIGeneratorTest.java @@ -0,0 +1,85 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.doi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.mycore.access.MCRAccessBaseImpl; +import org.mycore.common.MCRTestConfiguration; +import org.mycore.common.MCRTestProperty; +import org.mycore.common.date.MCRDateFormatter; +import org.mycore.common.date.MCRFLDateScrambler; +import org.mycore.datamodel.metadata.MCRObject; +import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.common.date.MCRMockDateFormatter; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; +import org.mycore.test.MyCoReTest; + +@MyCoReTest +@MCRTestConfiguration(properties = { + @MCRTestProperty(key = "MCR.Access.Class", classNameOf = MCRAccessBaseImpl.class), + @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true"), +}) +public class MCRCurrentDateDOIGeneratorTest { + + public static final String PREFIX = "10.1234"; + + @Test + public void generate() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRMockDateFormatter formatter = new MCRMockDateFormatter(); + MCRCurrentDateDOIGenerator generator = new MCRCurrentDateDOIGenerator(new MCRDOIParser(), formatter, PREFIX); + String doi = generator.generate(object, "").asString(); + + assertTrue(doi.startsWith(PREFIX)); + assertEquals('/', doi.charAt(PREFIX.length())); + + String value = doi.substring(PREFIX.length() + 1); + + assertEquals(formatter.lastFormattedDate(), value); + + } + + @Test + public void generateMultiple() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRDateFormatter formatter = new MCRFLDateScrambler(); + MCRCurrentDateDOIGenerator generator = new MCRCurrentDateDOIGenerator(new MCRDOIParser(), formatter, PREFIX); + String doi1 = generator.generate(object, "").asString(); + String doi2 = generator.generate(object, "").asString(); + String doi3 = generator.generate(object, "").asString(); + + assertNotEquals(doi1, doi2); + assertNotEquals(doi2, doi3); + assertNotEquals(doi3, doi1); + + } + +} diff --git a/mycore-pi/src/test/java/org/mycore/pi/doi/MCRMapObjectIDDOIGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/doi/MCRMapObjectIDDOIGeneratorTest.java index 08c42765df..ba3ef98c43 100644 --- a/mycore-pi/src/test/java/org/mycore/pi/doi/MCRMapObjectIDDOIGeneratorTest.java +++ b/mycore-pi/src/test/java/org/mycore/pi/doi/MCRMapObjectIDDOIGeneratorTest.java @@ -20,60 +20,77 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mycore.pi.doi.MCRDigitalObjectIdentifier.TEST_DOI_PREFIX; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mycore.common.MCRTestConfiguration; import org.mycore.common.MCRTestProperty; -import org.mycore.common.config.MCRConfiguration2; import org.mycore.datamodel.metadata.MCRObject; import org.mycore.datamodel.metadata.MCRObjectID; import org.mycore.pi.exceptions.MCRPersistentIdentifierException; import org.mycore.test.MyCoReTest; -/** - * @author Thomas Scheffler (yagee) - */ @MyCoReTest @MCRTestConfiguration(properties = { @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true"), - @MCRTestProperty(key = "MCR.PI.Generator.MapObjectIDDOI.Class", classNameOf = MCRMapObjectIDDOIGenerator.class), - @MCRTestProperty(key = "MCR.PI.Generator.MapObjectIDDOI.Prefix.junit_test", string = TEST_DOI_PREFIX), - @MCRTestProperty(key = "MCR.PI.Generator.MapObjectIDDOI.Prefix.my_test", string = TEST_DOI_PREFIX + "/my.") }) public class MCRMapObjectIDDOIGeneratorTest { - MCRMapObjectIDDOIGenerator doiGenerator; + public static final String PREFIX = "10.1234"; + + @Test + public void generate() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + Map prefixMap = Map.of("my_test", PREFIX); + MCRMapObjectIDDOIGenerator generator = new MCRMapObjectIDDOIGenerator(new MCRDOIParser(), prefixMap); + String doi = generator.generate(object, "").asString(); + + assertTrue(doi.startsWith(PREFIX)); + assertEquals('/', doi.charAt(PREFIX.length())); + + String value = doi.substring(PREFIX.length() + 1); + + assertEquals("123", value); - @BeforeEach - public void setUp() { - doiGenerator = MCRConfiguration2.getInstanceOfOrThrow( - MCRMapObjectIDDOIGenerator.class, "MCR.PI.Generator.MapObjectIDDOI"); } @Test - public void generate() throws Exception { - MCRObjectID testID1 = MCRObjectID.getInstance("junit_test_00004711"); - MCRObject mcrObject1 = new MCRObject(); - mcrObject1.setId(testID1); - MCRObjectID testID2 = MCRObjectID.getInstance("my_test_00000815"); - MCRObject mcrObject2 = new MCRObject(); - mcrObject2.setId(testID2); - assertEquals(TEST_DOI_PREFIX + "/4711", doiGenerator.generate(mcrObject1, null).asString()); - assertEquals(TEST_DOI_PREFIX + "/my.815", doiGenerator.generate(mcrObject2, null).asString()); + public void generateWithInfix() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + Map prefixMap = Map.of("my_test", PREFIX + "/xyz-"); + MCRMapObjectIDDOIGenerator generator = new MCRMapObjectIDDOIGenerator(new MCRDOIParser(), prefixMap); + String doi = generator.generate(object, "").asString(); + + assertTrue(doi.startsWith(PREFIX)); + assertEquals('/', doi.charAt(PREFIX.length())); + + String value = doi.substring(PREFIX.length() + 1); + + assertEquals("xyz-123", value); + } @Test - public void missingMappingTest() { - assertThrows( - MCRPersistentIdentifierException.class, - () -> { - MCRObjectID testID = MCRObjectID.getInstance("brandNew_test_00000001"); - MCRObject mcrObject = new MCRObject(); - mcrObject.setId(testID); - doiGenerator.generate(mcrObject, null); - }); + public void missingMapping() { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + Map prefixMap = Map.of(); + MCRMapObjectIDDOIGenerator generator = new MCRMapObjectIDDOIGenerator(new MCRDOIParser(), prefixMap); + + assertThrows(MCRPersistentIdentifierException.class, () -> generator.generate(object, "")); } } diff --git a/mycore-pi/src/test/java/org/mycore/pi/doi/MCROtherPIDOIGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/doi/MCROtherPIDOIGeneratorTest.java new file mode 100644 index 0000000000..f6c6205015 --- /dev/null +++ b/mycore-pi/src/test/java/org/mycore/pi/doi/MCROtherPIDOIGeneratorTest.java @@ -0,0 +1,88 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.doi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mycore.pi.MCRMockMetadataService.TEST_PROPERTY; +import static org.mycore.pi.MCRMockMetadataService.TEST_PROPERTY_VALUE; + +import java.util.concurrent.ExecutionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mycore.access.MCRAccessBaseImpl; +import org.mycore.access.MCRAccessException; +import org.mycore.common.MCRTestConfiguration; +import org.mycore.common.MCRTestProperty; +import org.mycore.datamodel.metadata.MCRObject; +import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.pi.MCRMockIdentifierGenerator; +import org.mycore.pi.MCRMockIdentifierService; +import org.mycore.pi.MCRMockMetadataService; +import org.mycore.pi.MCRPIService; +import org.mycore.pi.MCRPIServiceManager; +import org.mycore.pi.MCRPersistentIdentifier; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; +import org.mycore.pi.util.MCROtherPIValueExtractor; +import org.mycore.test.MCRJPAExtension; +import org.mycore.test.MyCoReTest; + +@MyCoReTest +@ExtendWith({ MCRJPAExtension.class }) +@MCRTestConfiguration(properties = { + @MCRTestProperty(key = "MCR.Access.Class", classNameOf = MCRAccessBaseImpl.class), + @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true"), + @MCRTestProperty(key = "MCR.PI.Service.Mock.Class", classNameOf = MCRMockIdentifierService.class), + @MCRTestProperty(key = "MCR.PI.Service.Mock.Generator", string = "Mock"), + @MCRTestProperty(key = "MCR.PI.Service.Mock.MetadataService", string = "Mock"), + @MCRTestProperty(key = "MCR.PI.Generator.Mock.Class", classNameOf = MCRMockIdentifierGenerator.class), + @MCRTestProperty(key = "MCR.PI.MetadataService.Mock.Class", classNameOf = MCRMockMetadataService.class), + @MCRTestProperty(key = "MCR.PI.MetadataService.Mock." + TEST_PROPERTY, string = TEST_PROPERTY_VALUE), +}) +public class MCROtherPIDOIGeneratorTest { + + public static final String PREFIX = "10.1234"; + + @Test + public void generate() + throws MCRPersistentIdentifierException, MCRAccessException, ExecutionException, InterruptedException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRPIServiceManager manager = MCRPIServiceManager.getInstance(); + MCRPIService mockService = manager.getRegistrationService("Mock"); + mockService.register(object, "", true); + + MCROtherPIValueExtractor extractor = new MCROtherPIValueExtractor("mock", "Mock", "MOCK:my_test_(.*):"); + MCROtherPIDOIGenerator generator = new MCROtherPIDOIGenerator(new MCRDOIParser(), PREFIX, extractor); + String doi = generator.generate(object, "").asString(); + + assertTrue(doi.startsWith(PREFIX)); + assertEquals('/', doi.charAt(PREFIX.length())); + + String value = doi.substring(PREFIX.length() + 1); + + assertEquals("00000123", value); + + } + +} diff --git a/mycore-pi/src/test/java/org/mycore/pi/doi/MCRUUIDDOIGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/doi/MCRUUIDDOIGeneratorTest.java new file mode 100644 index 0000000000..7b08673b82 --- /dev/null +++ b/mycore-pi/src/test/java/org/mycore/pi/doi/MCRUUIDDOIGeneratorTest.java @@ -0,0 +1,85 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.doi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; +import org.mycore.common.MCRTestConfiguration; +import org.mycore.common.MCRTestProperty; +import org.mycore.datamodel.metadata.MCRObject; +import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; +import org.mycore.test.MyCoReTest; + +@MyCoReTest +@MCRTestConfiguration(properties = { + @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true"), +}) +public class MCRUUIDDOIGeneratorTest { + + public static final String PREFIX = "10.1234"; + + @Test + public void generate() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRUUIDDOIGenerator generator = new MCRUUIDDOIGenerator(new MCRDOIParser(), PREFIX); + String doi = generator.generate(object, "").asString(); + + assertTrue(doi.startsWith(PREFIX)); + assertEquals('/', doi.charAt(PREFIX.length())); + + String uuid = doi.substring(PREFIX.length() + 1); + + try { + UUID.fromString(uuid); + } catch (Exception e) { + fail("NISS is not a valid UUID", e); + } + + } + + @Test + public void generateMultiple() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRUUIDDOIGenerator generator = new MCRUUIDDOIGenerator(new MCRDOIParser(), PREFIX); + String doi1 = generator.generate(object, "").asString(); + String doi2 = generator.generate(object, "").asString(); + String doi3 = generator.generate(object, "").asString(); + + assertNotEquals(doi1, doi2); + assertNotEquals(doi2, doi3); + assertNotEquals(doi3, doi1); + + } + +} diff --git a/mycore-pi/src/test/java/org/mycore/pi/purl/MCRIDPURLGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/purl/MCRIDPURLGeneratorTest.java index de7d93be54..8e508a4410 100644 --- a/mycore-pi/src/test/java/org/mycore/pi/purl/MCRIDPURLGeneratorTest.java +++ b/mycore-pi/src/test/java/org/mycore/pi/purl/MCRIDPURLGeneratorTest.java @@ -19,12 +19,10 @@ package org.mycore.pi.purl; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mycore.pi.MCRPIService.GENERATOR_CONFIG_PREFIX; import org.junit.jupiter.api.Test; import org.mycore.common.MCRTestConfiguration; import org.mycore.common.MCRTestProperty; -import org.mycore.common.config.MCRConfiguration2; import org.mycore.datamodel.metadata.MCRObject; import org.mycore.datamodel.metadata.MCRObjectID; import org.mycore.pi.exceptions.MCRPersistentIdentifierException; @@ -33,40 +31,34 @@ @MyCoReTest @MCRTestConfiguration(properties = { @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true"), - @MCRTestProperty(key = GENERATOR_CONFIG_PREFIX + MCRIDPURLGeneratorTest.GENERATOR_1 + ".Class", - classNameOf = MCRIDPURLGenerator.class), - @MCRTestProperty(key = GENERATOR_CONFIG_PREFIX + MCRIDPURLGeneratorTest.GENERATOR_1 + ".BaseURLTemplate", - string = MCRIDPURLGeneratorTest.TEST_BASE_1), - @MCRTestProperty(key = GENERATOR_CONFIG_PREFIX + MCRIDPURLGeneratorTest.GENERATOR_2 + ".Class", - classNameOf = MCRIDPURLGenerator.class), - @MCRTestProperty(key = GENERATOR_CONFIG_PREFIX + MCRIDPURLGeneratorTest.GENERATOR_2 + ".BaseURLTemplate", - string = MCRIDPURLGeneratorTest.TEST_BASE_2) }) public class MCRIDPURLGeneratorTest { - public static final String TEST_BASE_1 = "http://purl.myurl.de/$ID"; + @Test + public void generate() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); - public static final String TEST_BASE_2 = "http://purl.myurl.de/$ID/$ID/$ID"; + MCRIDPURLGenerator generator = new MCRIDPURLGenerator("https://purl.example.com/$ID"); + String purl = generator.generate(object, "").asString(); - public static final String GENERATOR_1 = "IDPURLGenerator"; + assertEquals("https://purl.example.com/my_test_00000123", purl, ""); - public static final String GENERATOR_2 = GENERATOR_1 + "2"; + } @Test - public void generate() throws MCRPersistentIdentifierException { - MCRObjectID testID = MCRObjectID.getInstance("my_test_00000001"); - MCRObject mcrObject = new MCRObject(); - mcrObject.setId(testID); + public void generateWithMultipleReplacements() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); - MCRIDPURLGenerator generator1 = MCRConfiguration2.getInstanceOfOrThrow( - MCRIDPURLGenerator.class, GENERATOR_CONFIG_PREFIX + GENERATOR_1); - assertEquals("http://purl.myurl.de/my_test_00000001", - generator1.generate(mcrObject, "").asString(), ""); + MCRIDPURLGenerator generator = new MCRIDPURLGenerator("https://purl.example.com/$ID/$ID/XYZ"); + String purl = generator.generate(object, "").asString(); - MCRIDPURLGenerator generator2 = MCRConfiguration2.getInstanceOfOrThrow( - MCRIDPURLGenerator.class, GENERATOR_CONFIG_PREFIX + GENERATOR_2); - assertEquals("http://purl.myurl.de/my_test_00000001/my_test_00000001/my_test_00000001", - generator2.generate(mcrObject, "").asString(), ""); + assertEquals("https://purl.example.com/my_test_00000123/my_test_00000123/XYZ", purl, ""); } diff --git a/mycore-pi/src/test/java/org/mycore/pi/urn/MCRCreateDateDNBURNGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/urn/MCRCreateDateDNBURNGeneratorTest.java new file mode 100644 index 0000000000..9b93555f34 --- /dev/null +++ b/mycore-pi/src/test/java/org/mycore/pi/urn/MCRCreateDateDNBURNGeneratorTest.java @@ -0,0 +1,94 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.urn; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mycore.common.MCRTestConfiguration; +import org.mycore.common.MCRTestProperty; +import org.mycore.common.date.MCRDateFormatter; +import org.mycore.common.date.MCRFLDateScrambler; +import org.mycore.common.date.MCRMockDateFormatter; +import org.mycore.datamodel.metadata.MCRObject; +import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; +import org.mycore.test.MCRJPAExtension; +import org.mycore.test.MyCoReTest; + +@MyCoReTest +@ExtendWith({ MCRJPAExtension.class }) +@MCRTestConfiguration(properties = { + @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true"), +}) +public class MCRCreateDateDNBURNGeneratorTest { + + public static final String NAMESPACE = "urn:nbn:de:gbv:xyz"; + + @Test + public void generate() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRMockDateFormatter formatter = new MCRMockDateFormatter(); + MCRCreateDateDNBURNGenerator generator = new MCRCreateDateDNBURNGenerator(formatter, NAMESPACE, "-", 3); + String urn = generator.generate(object, "").asString(); + + assertTrue(urn.startsWith(NAMESPACE)); + assertEquals('-', urn.charAt(NAMESPACE.length())); + assertEquals('-', urn.charAt(urn.length() - 2)); + + String value = urn.substring(NAMESPACE.length() + 1, urn.length() - 2); + char checksum = Character.forDigit(new MCRDNBURN("gbv:xyz", "-" + value + "-").calculateChecksum(), 10); + + assertTrue(value.startsWith(formatter.lastFormattedDate() + "-")); + assertTrue(value.endsWith("-000")); + assertEquals(checksum, urn.charAt(urn.length() - 1)); + + } + + @Test + public void generateMultiple() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRDateFormatter formatter = new MCRFLDateScrambler(); + MCRCreateDateDNBURNGenerator generator = new MCRCreateDateDNBURNGenerator(formatter, NAMESPACE, "", -1); + String urn1 = generator.generate(object, "").asString(); + String urn2 = generator.generate(object, "").asString(); + String urn3 = generator.generate(object, "").asString(); + + assertNotEquals(urn1, urn2); + assertNotEquals(urn2, urn3); + assertNotEquals(urn3, urn1); + + assertTrue(urn1.substring(0, urn1.length() - 1).endsWith("-0")); + assertTrue(urn2.substring(0, urn2.length() - 1).endsWith("-1")); + assertTrue(urn3.substring(0, urn3.length() - 1).endsWith("-2")); + + } + +} diff --git a/mycore-pi/src/test/java/org/mycore/pi/urn/MCRCurrentDateDNBURNGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/urn/MCRCurrentDateDNBURNGeneratorTest.java new file mode 100644 index 0000000000..88494a8098 --- /dev/null +++ b/mycore-pi/src/test/java/org/mycore/pi/urn/MCRCurrentDateDNBURNGeneratorTest.java @@ -0,0 +1,88 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.urn; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.mycore.access.MCRAccessBaseImpl; +import org.mycore.common.MCRTestConfiguration; +import org.mycore.common.MCRTestProperty; +import org.mycore.common.date.MCRDateFormatter; +import org.mycore.common.date.MCRFLDateScrambler; +import org.mycore.datamodel.metadata.MCRObject; +import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.common.date.MCRMockDateFormatter; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; +import org.mycore.test.MyCoReTest; + +@MyCoReTest +@MCRTestConfiguration(properties = { + @MCRTestProperty(key = "MCR.Access.Class", classNameOf = MCRAccessBaseImpl.class), + @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true"), +}) +public class MCRCurrentDateDNBURNGeneratorTest { + + public static final String NAMESPACE = "urn:nbn:de:gbv:xyz"; + + @Test + public void generate() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRMockDateFormatter formatter = new MCRMockDateFormatter(); + MCRCurrentDateDNBURNGenerator generator = new MCRCurrentDateDNBURNGenerator(formatter, NAMESPACE, "-"); + String urn = generator.generate(object, "").asString(); + + assertTrue(urn.startsWith(NAMESPACE)); + assertEquals('-', urn.charAt(NAMESPACE.length())); + assertEquals('-', urn.charAt(urn.length() - 2)); + + String value = urn.substring(NAMESPACE.length() + 1, urn.length() - 2); + char checksum = Character.forDigit(new MCRDNBURN("gbv:xyz", "-" + value + "-").calculateChecksum(), 10); + + assertEquals(formatter.lastFormattedDate(), value); + assertEquals(checksum, urn.charAt(urn.length() - 1)); + + } + + @Test + public void generateMultiple() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRDateFormatter formatter = new MCRFLDateScrambler(); + MCRCurrentDateDNBURNGenerator generator = new MCRCurrentDateDNBURNGenerator(formatter, NAMESPACE, "-"); + String urn1 = generator.generate(object, "").asString(); + String urn2 = generator.generate(object, "").asString(); + String urn3 = generator.generate(object, "").asString(); + + assertNotEquals(urn1, urn2); + assertNotEquals(urn2, urn3); + assertNotEquals(urn3, urn1); + + } + +} diff --git a/mycore-pi/src/test/java/org/mycore/pi/urn/MCRDNBURNGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/urn/MCRDNBURNGeneratorTest.java deleted file mode 100644 index c4538ad9bf..0000000000 --- a/mycore-pi/src/test/java/org/mycore/pi/urn/MCRDNBURNGeneratorTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This file is part of *** M y C o R e *** - * See https://www.mycore.de/ for details. - * - * MyCoRe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MyCoRe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MyCoRe. If not, see . - */ - -package org.mycore.pi.urn; - -import static org.junit.jupiter.api.Assertions.assertFalse; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mycore.access.MCRAccessBaseImpl; -import org.mycore.common.MCRTestConfiguration; -import org.mycore.common.MCRTestProperty; -import org.mycore.common.config.MCRConfiguration2; -import org.mycore.datamodel.metadata.MCRMetadataManager; -import org.mycore.datamodel.metadata.MCRObject; -import org.mycore.datamodel.metadata.MCRObjectID; -import org.mycore.test.MCRJPAExtension; -import org.mycore.test.MCRMetadataExtension; -import org.mycore.test.MyCoReTest; - -@MyCoReTest -@ExtendWith(MCRJPAExtension.class) -@ExtendWith(MCRMetadataExtension.class) -@MCRTestConfiguration(properties = { - @MCRTestProperty(key = "MCR.Access.Class", classNameOf = MCRAccessBaseImpl.class), - @MCRTestProperty(key = "MCR.Metadata.Type.mock", string = "true"), - @MCRTestProperty(key = "MCR.Metadata.Type.unregisterd", string = "true"), - @MCRTestProperty(key = "MCR.PI.Generator." + MCRDNBURNGeneratorTest.GENERATOR_ID + ".Class", - classNameOf = MCRFLURNGenerator.class), - @MCRTestProperty(key = "MCR.PI.Generator." + MCRDNBURNGeneratorTest.GENERATOR_ID + ".Namespace", - string = "urn:nbn:de:gbv") -}) -public class MCRDNBURNGeneratorTest { - - public static final String GENERATOR_ID = "TESTDNBURN1"; - - private static final Logger LOGGER = LogManager.getLogger(); - - @Test - public void generate() { - MCRObjectID getID = MCRMetadataManager.getMCRObjectIDGenerator().getNextFreeId("test", "mock"); - MCRObject mcrObject1 = new MCRObject(); - mcrObject1.setId(getID); - MCRFLURNGenerator flGenerator = MCRConfiguration2.getInstanceOfOrThrow( - MCRFLURNGenerator.class, "MCR.PI.Generator." + GENERATOR_ID); - MCRDNBURN generated = flGenerator.generate(mcrObject1, ""); - - String urn = generated.asString(); - LOGGER.info("THE URN IS: {}", urn); - - assertFalse(urn.startsWith("urn:nbn:de:urn:nbn:de")); - } - -} diff --git a/mycore-pi/src/test/java/org/mycore/pi/urn/MCROtherPIDNBURNGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/urn/MCROtherPIDNBURNGeneratorTest.java new file mode 100644 index 0000000000..d6c211184c --- /dev/null +++ b/mycore-pi/src/test/java/org/mycore/pi/urn/MCROtherPIDNBURNGeneratorTest.java @@ -0,0 +1,91 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.urn; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mycore.pi.MCRMockMetadataService.TEST_PROPERTY; +import static org.mycore.pi.MCRMockMetadataService.TEST_PROPERTY_VALUE; + +import java.util.concurrent.ExecutionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mycore.access.MCRAccessBaseImpl; +import org.mycore.access.MCRAccessException; +import org.mycore.common.MCRTestConfiguration; +import org.mycore.common.MCRTestProperty; +import org.mycore.datamodel.metadata.MCRObject; +import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.pi.MCRMockIdentifierGenerator; +import org.mycore.pi.MCRMockIdentifierService; +import org.mycore.pi.MCRMockMetadataService; +import org.mycore.pi.MCRPIService; +import org.mycore.pi.MCRPIServiceManager; +import org.mycore.pi.MCRPersistentIdentifier; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; +import org.mycore.pi.util.MCROtherPIValueExtractor; +import org.mycore.test.MCRJPAExtension; +import org.mycore.test.MyCoReTest; + +@MyCoReTest +@ExtendWith({ MCRJPAExtension.class }) +@MCRTestConfiguration(properties = { + @MCRTestProperty(key = "MCR.Access.Class", classNameOf = MCRAccessBaseImpl.class), + @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true"), + @MCRTestProperty(key = "MCR.PI.Service.Mock.Class", classNameOf = MCRMockIdentifierService.class), + @MCRTestProperty(key = "MCR.PI.Service.Mock.Generator", string = "Mock"), + @MCRTestProperty(key = "MCR.PI.Service.Mock.MetadataService", string = "Mock"), + @MCRTestProperty(key = "MCR.PI.Generator.Mock.Class", classNameOf = MCRMockIdentifierGenerator.class), + @MCRTestProperty(key = "MCR.PI.MetadataService.Mock.Class", classNameOf = MCRMockMetadataService.class), + @MCRTestProperty(key = "MCR.PI.MetadataService.Mock." + TEST_PROPERTY, string = TEST_PROPERTY_VALUE), +}) +public class MCROtherPIDNBURNGeneratorTest { + + public static final String NAMESPACE = "urn:nbn:de:gbv:xyz"; + + @Test + public void generate() + throws MCRPersistentIdentifierException, MCRAccessException, ExecutionException, InterruptedException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRPIServiceManager manager = MCRPIServiceManager.getInstance(); + MCRPIService mockService = manager.getRegistrationService("Mock"); + mockService.register(object, "", true); + + MCROtherPIValueExtractor extractor = new MCROtherPIValueExtractor("mock", "Mock", "MOCK:my_test_(.*):"); + MCROtherPIDNBURNGenerator generator = new MCROtherPIDNBURNGenerator(NAMESPACE, "-", extractor); + String urn = generator.generate(object, "").asString(); + + assertTrue(urn.startsWith(NAMESPACE)); + assertEquals('-', urn.charAt(NAMESPACE.length())); + assertEquals('-', urn.charAt(urn.length() - 2)); + + String value = urn.substring(NAMESPACE.length() + 1, urn.length() - 2); + char checksum = Character.forDigit(new MCRDNBURN("gbv:xyz", "-" + value + "-").calculateChecksum(), 10); + + assertEquals("00000123", value); + assertEquals(checksum, urn.charAt(urn.length() - 1)); + + } + +} diff --git a/mycore-pi/src/test/java/org/mycore/pi/urn/MCRUUIDDNBURNGeneratorTest.java b/mycore-pi/src/test/java/org/mycore/pi/urn/MCRUUIDDNBURNGeneratorTest.java new file mode 100644 index 0000000000..673fdc0eac --- /dev/null +++ b/mycore-pi/src/test/java/org/mycore/pi/urn/MCRUUIDDNBURNGeneratorTest.java @@ -0,0 +1,88 @@ +/* + * This file is part of *** M y C o R e *** + * See https://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +package org.mycore.pi.urn; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; +import org.mycore.common.MCRTestConfiguration; +import org.mycore.common.MCRTestProperty; +import org.mycore.datamodel.metadata.MCRObject; +import org.mycore.datamodel.metadata.MCRObjectID; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; +import org.mycore.test.MyCoReTest; + +@MyCoReTest +@MCRTestConfiguration(properties = { + @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true"), +}) +public class MCRUUIDDNBURNGeneratorTest { + + public static final String NAMESPACE = "urn:nbn:de:gbv:xyz"; + + @Test + public void generate() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRUUIDDNBURNGenerator generator = new MCRUUIDDNBURNGenerator(NAMESPACE, "-"); + String urn = generator.generate(object, "").asString(); + + assertTrue(urn.startsWith(NAMESPACE)); + assertEquals('-', urn.charAt(NAMESPACE.length())); + assertEquals('-', urn.charAt(urn.length() - 2)); + + String uuid = urn.substring(NAMESPACE.length() + 1, urn.length() - 2); + char checksum = Character.forDigit(new MCRDNBURN("gbv:xyz", "-" + uuid + "-").calculateChecksum(), 10); + + try { + UUID.fromString(uuid); + } catch (Exception e) { + fail("NISS is not a valid UUID", e); + } + assertEquals(checksum, urn.charAt(urn.length() - 1)); + + } + + @Test + public void generateMultiple() throws MCRPersistentIdentifierException { + + MCRObject object = new MCRObject(); + object.setSchema("http://www.w3.org/2001/XMLSchema"); + object.setId(MCRObjectID.getInstance("my_test_00000123")); + + MCRUUIDDNBURNGenerator generator = new MCRUUIDDNBURNGenerator(NAMESPACE, "-"); + String urn1 = generator.generate(object, "").asString(); + String urn2 = generator.generate(object, "").asString(); + String urn3 = generator.generate(object, "").asString(); + + assertNotEquals(urn1, urn2); + assertNotEquals(urn2, urn3); + assertNotEquals(urn3, urn1); + + } + +} diff --git a/mycore-pi/src/test/java/org/mycore/pi/urn/rest/MCRURNGranularRESTRegistrationTaskTest.java b/mycore-pi/src/test/java/org/mycore/pi/urn/rest/MCRURNGranularRESTRegistrationTaskTest.java index 963fe68a21..9ef7b55783 100644 --- a/mycore-pi/src/test/java/org/mycore/pi/urn/rest/MCRURNGranularRESTRegistrationTaskTest.java +++ b/mycore-pi/src/test/java/org/mycore/pi/urn/rest/MCRURNGranularRESTRegistrationTaskTest.java @@ -38,6 +38,7 @@ import org.mycore.pi.MCRPIRegistrationInfo; import org.mycore.pi.MCRPIUtils; import org.mycore.pi.backend.MCRPI; +import org.mycore.pi.exceptions.MCRPersistentIdentifierException; import org.mycore.pi.urn.MCRDNBURN; import org.mycore.test.MCRMetadataExtension; import org.mycore.test.MyCoReTest; @@ -50,8 +51,7 @@ @MyCoReTest @ExtendWith(MCRMetadataExtension.class) @MCRTestConfiguration(properties = { - @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true"), - @MCRTestProperty(key = "MCR.PI.Generator.testGenerator.Namespace", string = "frontend-") + @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true") }) public class MCRURNGranularRESTRegistrationTaskTest { private static final String countRegistered = "select count(u) from MCRPI u " @@ -64,10 +64,9 @@ public class MCRURNGranularRESTRegistrationTaskTest { @Disabled @Test - public void run() { - MCRPI urn1 = generateMCRPI(randomFilename(), countRegistered); - MCREntityManagerProvider.getCurrentEntityManager() - .persist(urn1); + public void run() throws MCRPersistentIdentifierException { + MCRPI urn1 = generateMCRPI(randomFilename(), countRegistered, "frontend-"); + MCREntityManagerProvider.getCurrentEntityManager().persist(urn1); assertNull(urn1.getRegistered(), "Registered date should be null."); diff --git a/mycore-pi/src/test/java/org/mycore/pi/urn/rest/MCRURNGranularRESTServiceTest.java b/mycore-pi/src/test/java/org/mycore/pi/urn/rest/MCRURNGranularRESTServiceTest.java index 7df0fd9942..b4f2f68eab 100644 --- a/mycore-pi/src/test/java/org/mycore/pi/urn/rest/MCRURNGranularRESTServiceTest.java +++ b/mycore-pi/src/test/java/org/mycore/pi/urn/rest/MCRURNGranularRESTServiceTest.java @@ -58,7 +58,7 @@ import org.mycore.datamodel.niofs.MCRPath; import org.mycore.pi.MCRPIRegistrationInfo; import org.mycore.pi.urn.MCRDNBURN; -import org.mycore.pi.urn.MCRUUIDURNGenerator; +import org.mycore.pi.urn.MCRUUIDDNBURNGenerator; import org.mycore.test.MCRJPAExtension; import org.mycore.test.MCRMetadataExtension; import org.mycore.test.MyCoReTest; @@ -82,7 +82,7 @@ @MCRTestProperty(key = "MCR.IFS2.Store.mycore_derivate.SlotLayout", string = "4-2-2"), @MCRTestProperty(key = "MCR.EventHandler.MCRDerivate.020.Class", classNameOf = MCRXMLMetadataEventHandler.class), @MCRTestProperty(key = "MCR.EventHandler.MCRDerivate.030.Class", classNameOf = MCRLinkTableEventHandler.class), - @MCRTestProperty(key = "MCR.PI.Generator.UUID.Class", classNameOf = MCRUUIDURNGenerator.class), + @MCRTestProperty(key = "MCR.PI.Generator.UUID.Class", classNameOf = MCRUUIDDNBURNGenerator.class), @MCRTestProperty(key = "MCR.PI.Generator.UUID.Namespace", string = "frontend-"), @MCRTestProperty(key = "MCR.PI.DNB.Credentials.Login", string = "test"), @MCRTestProperty(key = "MCR.PI.DNB.Credentials.Password", string = "test") diff --git a/pom.xml b/pom.xml index 7ef1d0de1b..cc9d968cc7 100644 --- a/pom.xml +++ b/pom.xml @@ -1166,6 +1166,11 @@ Applications based on MyCoRe use a common core, which provides the functionality sass-embedded-protocol 4.4.0 + + de.thetaphi + forbiddenapis + ${forbiddenapis.version} + de.undercouch citeproc-java