From 8075060c3b4e65f45efdc4c1b711409dddbd989e Mon Sep 17 00:00:00 2001 From: Oleg Stobbe <oleg@openslx.com> Date: Thu, 23 Nov 2023 16:05:06 +0100 Subject: [PATCH 1/6] Rework logging in object-archives to use prefix-loggers with contexts --- .../conf/ObjectArchiveSingleton.java | 4 +++- .../impl/DigitalObjectArchiveBase.java | 20 ++++++++++++++++ .../impl/DigitalObjectFileArchive.java | 15 ++++++++---- .../impl/DigitalObjectMETSFileArchive.java | 10 ++++---- .../impl/DigitalObjectS3Archive.java | 23 ++++++------------- 5 files changed, 46 insertions(+), 26 deletions(-) create mode 100644 src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectArchiveBase.java diff --git a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/conf/ObjectArchiveSingleton.java b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/conf/ObjectArchiveSingleton.java index 4319d16844..f454afcb64 100644 --- a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/conf/ObjectArchiveSingleton.java +++ b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/conf/ObjectArchiveSingleton.java @@ -65,7 +65,9 @@ import de.bwl.bwfla.objectarchive.impl.DigitalObjectFileArchive; public class ObjectArchiveSingleton implements IMigratable { - protected static final Logger LOG = Logger.getLogger(ObjectArchiveSingleton.class.getName()); + public static final String LOGGER_NAME = "OBJECT-ARCHIVE"; + protected static final Logger LOG = Logger.getLogger(LOGGER_NAME); + public static volatile boolean confValid = false; //public static volatile ObjectArchiveConf CONF; public static ConcurrentHashMap<String, DigitalObjectArchive> archiveMap = null; diff --git a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectArchiveBase.java b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectArchiveBase.java new file mode 100644 index 0000000000..3fd22ced72 --- /dev/null +++ b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectArchiveBase.java @@ -0,0 +1,20 @@ +package de.bwl.bwfla.objectarchive.impl; + +import de.bwl.bwfla.common.logging.PrefixLogger; +import de.bwl.bwfla.common.logging.PrefixLoggerContext; +import de.bwl.bwfla.objectarchive.conf.ObjectArchiveSingleton; +import de.bwl.bwfla.objectarchive.datatypes.DigitalObjectArchive; + + +public abstract class DigitalObjectArchiveBase implements DigitalObjectArchive +{ + protected final PrefixLogger log; + + protected DigitalObjectArchiveBase(String type) + { + final var logctx = new PrefixLoggerContext() + .add("type", type); + + this.log = new PrefixLogger(ObjectArchiveSingleton.LOGGER_NAME, logctx); + } +} diff --git a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectFileArchive.java b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectFileArchive.java index 9292297fce..5888c0a6c5 100644 --- a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectFileArchive.java +++ b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectFileArchive.java @@ -39,7 +39,6 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -75,10 +74,9 @@ import static de.bwl.bwfla.common.utils.METS.MetsUtil.MetsEaasConstant.FILE_GROU // FIXME: this class should be implemented in a style of a "Builder" pattern -public class DigitalObjectFileArchive implements Serializable, DigitalObjectArchive +public class DigitalObjectFileArchive extends DigitalObjectArchiveBase implements Serializable { private static final long serialVersionUID = -3958997016973537612L; - protected final Logger log = Logger.getLogger(this.getClass().getName()); private String name; private String localPath; @@ -109,13 +107,20 @@ public class DigitalObjectFileArchive implements Serializable, DigitalObjectArch */ public DigitalObjectFileArchive(String name, String localPath, boolean defaultArchive) { + this(); this.init(name, localPath, defaultArchive); } - protected DigitalObjectFileArchive() {} + protected DigitalObjectFileArchive() + { + super("file"); + } protected void init(String name, String localPath, boolean defaultArchive) { + log.getContext() + .add("name", name); + var httpExport = ConfigurationProvider.getConfiguration() .get("objectarchive.httpexport"); @@ -633,7 +638,7 @@ public class DigitalObjectFileArchive implements Serializable, DigitalObjectArch @Override public String resolveObjectResource(String objectId, String resourceId, String method) throws BWFLAException { - final var url = DigitalObjectArchive.super.resolveObjectResource(objectId, resourceId, method); + final var url = super.resolveObjectResource(objectId, resourceId, method); if (url == null || DataResolver.isAbsoluteUrl(url)) return url; diff --git a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectMETSFileArchive.java b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectMETSFileArchive.java index c8f05ac1ff..442398daf2 100644 --- a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectMETSFileArchive.java +++ b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectMETSFileArchive.java @@ -43,14 +43,12 @@ import java.nio.file.Paths; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Stream; -public class DigitalObjectMETSFileArchive implements Serializable, DigitalObjectArchive +public class DigitalObjectMETSFileArchive extends DigitalObjectArchiveBase implements Serializable { private static final long serialVersionUID = -3958997016973537612L; - protected final Logger log = Logger.getLogger(this.getClass().getName()); final private String name; final private File metaDataDir; @@ -60,6 +58,10 @@ public class DigitalObjectMETSFileArchive implements Serializable, DigitalObject private Map<String, MetsObject> objects; public DigitalObjectMETSFileArchive(String name, String metaDataPath, String dataPath, boolean defaultArchive) throws BWFLAException { + super("mets"); + log.getContext() + .add("name", name); + this.name = name; this.metaDataDir = new File(metaDataPath); if(!metaDataDir.exists() && !metaDataDir.isDirectory()) @@ -216,7 +218,7 @@ public class DigitalObjectMETSFileArchive implements Serializable, DigitalObject @Override public String resolveObjectResource(String objectId, String resourceId, String method) throws BWFLAException { - final var url = DigitalObjectArchive.super.resolveObjectResource(objectId, resourceId, method); + final var url = super.resolveObjectResource(objectId, resourceId, method); if (url == null || DataResolver.isAbsoluteUrl(url)) return url; diff --git a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java index 6892af659b..af95a68ff7 100644 --- a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java +++ b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java @@ -40,7 +40,6 @@ import de.bwl.bwfla.emucomp.api.EmulatorUtils; import de.bwl.bwfla.emucomp.api.FileCollection; import de.bwl.bwfla.emucomp.api.FileCollectionEntry; import de.bwl.bwfla.objectarchive.conf.ObjectArchiveSingleton; -import de.bwl.bwfla.objectarchive.datatypes.DigitalObjectArchive; import de.bwl.bwfla.objectarchive.datatypes.DigitalObjectFileMetadata; import de.bwl.bwfla.objectarchive.datatypes.DigitalObjectS3ArchiveDescriptor; import de.bwl.bwfla.objectarchive.datatypes.MetsObject; @@ -78,10 +77,8 @@ import java.util.stream.Stream; import static de.bwl.bwfla.objectarchive.impl.DigitalObjectFileArchive.UpdateCounts; -public class DigitalObjectS3Archive implements Serializable, DigitalObjectArchive +public class DigitalObjectS3Archive extends DigitalObjectArchiveBase implements Serializable { - protected final Logger log = Logger.getLogger(this.getClass().getName()); - private DigitalObjectS3ArchiveDescriptor descriptor; private Bucket bucket; private String basename; @@ -111,17 +108,8 @@ public class DigitalObjectS3Archive implements Serializable, DigitalObjectArchiv public DigitalObjectS3Archive(DigitalObjectS3ArchiveDescriptor descriptor) throws BWFLAException { - this.init(descriptor); - } - - protected DigitalObjectS3Archive() - { - // Empty! - } + super("s3"); - protected void init(DigitalObjectS3ArchiveDescriptor descriptor) - throws BWFLAException - { ConfigurationInjection.getConfigurationInjector() .configure(this); @@ -139,6 +127,9 @@ public class DigitalObjectS3Archive implements Serializable, DigitalObjectArchiv .toString(); } + log.getContext() + .add("name", basename); + this.driveMapper = new DriveMapper(); this.cache = this.load(); } @@ -670,7 +661,7 @@ public class DigitalObjectS3Archive implements Serializable, DigitalObjectArchiv @Override public String resolveObjectResource(String objectId, String resourceId, String method) throws BWFLAException { - var url = DigitalObjectArchive.super.resolveObjectResource(objectId, resourceId, method); + var url = super.resolveObjectResource(objectId, resourceId, method); if (url == null || DataResolver.isAbsoluteUrl(url)) return url; @@ -682,7 +673,7 @@ public class DigitalObjectS3Archive implements Serializable, DigitalObjectArchiv @Override public String resolveObjectResourceInternally(String objectId, String resourceId, String method) throws BWFLAException { - var url = DigitalObjectArchive.super.resolveObjectResourceInternally(objectId, resourceId, method); + var url = super.resolveObjectResourceInternally(objectId, resourceId, method); if (url == null || DataResolver.isAbsoluteUrl(url)){ return url; } -- GitLab From de37a4d8ee21d0b4783f80deeb0506dad049daf4 Mon Sep 17 00:00:00 2001 From: Oleg Stobbe <oleg@openslx.com> Date: Thu, 23 Nov 2023 16:28:41 +0100 Subject: [PATCH 2/6] Fix handling of custom path prefix in S3-based object-archive --- .../objectarchive/impl/DigitalObjectS3Archive.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java index af95a68ff7..fe0c92cea7 100644 --- a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java +++ b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java @@ -82,6 +82,7 @@ public class DigitalObjectS3Archive extends DigitalObjectArchiveBase implements private DigitalObjectS3ArchiveDescriptor descriptor; private Bucket bucket; private String basename; + private String basepath; private DriveMapper driveMapper; private Map<String, MetsObject> cache; @@ -123,9 +124,12 @@ public class DigitalObjectS3Archive extends DigitalObjectArchiveBase implements this.bucket = blobstore.bucket(descriptor.getBucket()); this.basename = DigitalObjectS3Archive.strSaveFilename(descriptor.getName()); if (descriptor.getPath() != null) { - this.basename = BlobStore.path(descriptor.getPath(), this.basename) + this.basepath = BlobStore.path(descriptor.getPath(), this.basename) .toString(); } + else { + this.basepath = this.basename; + } log.getContext() .add("name", basename); @@ -170,7 +174,7 @@ public class DigitalObjectS3Archive extends DigitalObjectArchiveBase implements private BlobStore.Path location(String id) { - return BlobStore.path(basename, id); + return BlobStore.path(basepath, id); } private BlobStore.Path resolveTarget(String id, ResourceType rt) @@ -347,7 +351,7 @@ public class DigitalObjectS3Archive extends DigitalObjectArchiveBase implements private Stream<String> listObjectIds() { - final var prefix = basename + "/"; + final var prefix = basepath + "/"; try { return bucket.list(prefix) @@ -1023,7 +1027,7 @@ public class DigitalObjectS3Archive extends DigitalObjectArchiveBase implements final var ocounter = UpdateCounts.counter(); final Function<Path, Integer> fuploader = (path) -> { - final var name = basename + "/" + basedir.relativize(path); + final var name = basepath + "/" + basedir.relativize(path); final var contentType = (name.endsWith(METS_MD_FILENAME)) ? MediaType.APPLICATION_XML : MediaType.APPLICATION_OCTET_STREAM; -- GitLab From 9e9a6f7bee2564de2b69442b5d52a124cb2a242f Mon Sep 17 00:00:00 2001 From: Oleg Stobbe <oleg@openslx.com> Date: Thu, 23 Nov 2023 16:42:13 +0100 Subject: [PATCH 3/6] Log storage bucket and path configured for S3-based object-archive --- .../bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java index fe0c92cea7..12f4056f6a 100644 --- a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java +++ b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java @@ -157,6 +157,7 @@ public class DigitalObjectS3Archive extends DigitalObjectArchiveBase implements } }; + log.info("Loading objects from bucket: s3://" + bucket.name() + "/" + basepath); this.listObjectIds() .forEach(downloader); @@ -470,7 +471,7 @@ public class DigitalObjectS3Archive extends DigitalObjectArchiveBase implements cache.put(mets.getID(), new MetsObject(metsdata)); - log.info("Object metadata uploaded to: " + blob.name()); + log.info("Object metadata uploaded to: s3://" + bucket.name() + "/" + blob.name()); } @Override -- GitLab From 80a3490ef736133e03a84e651bd1cd0985b14f8a Mon Sep 17 00:00:00 2001 From: Oleg Stobbe <oleg@openslx.com> Date: Thu, 23 Nov 2023 16:48:18 +0100 Subject: [PATCH 4/6] Log storage path configured for METS-based object-archive --- .../bwfla/objectarchive/impl/DigitalObjectMETSFileArchive.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectMETSFileArchive.java b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectMETSFileArchive.java index 442398daf2..36e2e43500 100644 --- a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectMETSFileArchive.java +++ b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectMETSFileArchive.java @@ -132,6 +132,7 @@ public class DigitalObjectMETSFileArchive extends DigitalObjectArchiveBase imple if (objects == null) objects = Collections.emptyMap(); + log.info("Loading objects from path: " + metaDataDir.getAbsolutePath()); for(File mets: metaDataDir.listFiles()) { try { -- GitLab From 84589c8f03bbb46b6e93f623e7782893090ba2a7 Mon Sep 17 00:00:00 2001 From: Oleg Stobbe <oleg@openslx.com> Date: Thu, 30 Nov 2023 20:31:11 +0100 Subject: [PATCH 5/6] Avoid modification of cached METS objects when exporting metadata --- .../de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java index 12f4056f6a..8d22f27d00 100644 --- a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java +++ b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java @@ -588,7 +588,7 @@ public class DigitalObjectS3Archive extends DigitalObjectArchiveBase implements private DigitalObjectMetadata getMetadataHelper(String objectId, BiFunction<String, String, String> metsExportPrefixer) throws BWFLAException { final var data = this.loadMetsData(objectId); - final var mets = MetsUtil.export(data.getMets(), metsExportPrefixer); + final var mets = MetsUtil.export(data.getMets(), metsExportPrefixer, metsExportPrefixer != null); final var md = new DigitalObjectMetadata(mets); md.markAsSoftware(data.isSoftware()); try { -- GitLab From f5cd83b5df8b7f68c0bb402917ec93c56f861cc1 Mon Sep 17 00:00:00 2001 From: Oleg Stobbe <oleg@openslx.com> Date: Tue, 12 Dec 2023 16:40:47 +0100 Subject: [PATCH 6/6] Add migration 'fix-mets-file-location-urls' --- .../conf/ObjectArchiveSingleton.java | 11 ++ .../impl/DigitalObjectS3Archive.java | 103 ++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/conf/ObjectArchiveSingleton.java b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/conf/ObjectArchiveSingleton.java index f454afcb64..524d879d0d 100644 --- a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/conf/ObjectArchiveSingleton.java +++ b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/conf/ObjectArchiveSingleton.java @@ -256,6 +256,7 @@ public class ObjectArchiveSingleton migrations.register("cleanup-legacy-object-files", this::cleanupLegacyObjectFiles); migrations.register("rename-user-object-archives", this::renameUserArchives); migrations.register("import-legacy-object-archives-v1", this::importLegacyArchivesV1); + migrations.register("fix-mets-file-location-urls", this::fixMetsFileLocationUrls); } private interface IHandler<T> @@ -355,4 +356,14 @@ public class ObjectArchiveSingleton usrarchive.importLegacyArchive(mc, Path.of(usrbasedir, name)); } } + + private void fixMetsFileLocationUrls(MigrationConfig mc) throws Exception + { + final IHandler<DigitalObjectArchive> migration = (archive) -> { + if (archive instanceof DigitalObjectS3Archive) + ((DigitalObjectS3Archive) archive).fixFileLocationUrls(mc); + }; + + this.execute(migration); + } } diff --git a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java index 8d22f27d00..512624c521 100644 --- a/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java +++ b/src/objectarchive/src/main/java/de/bwl/bwfla/objectarchive/impl/DigitalObjectS3Archive.java @@ -44,6 +44,7 @@ import de.bwl.bwfla.objectarchive.datatypes.DigitalObjectFileMetadata; import de.bwl.bwfla.objectarchive.datatypes.DigitalObjectS3ArchiveDescriptor; import de.bwl.bwfla.objectarchive.datatypes.MetsObject; import gov.loc.mets.Mets; +import gov.loc.mets.MetsType; import org.apache.tamaya.inject.ConfigurationInjection; import org.apache.tamaya.inject.api.Config; @@ -74,6 +75,7 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; +import static de.bwl.bwfla.common.utils.METS.MetsUtil.MetsEaasConstant.FILE_GROUP_OBJECTS; import static de.bwl.bwfla.objectarchive.impl.DigitalObjectFileArchive.UpdateCounts; @@ -1114,4 +1116,105 @@ public class DigitalObjectS3Archive extends DigitalObjectArchiveBase implements this.sync(); } + + public void fixFileLocationUrls(MigrationConfig mc) throws Exception + { + final var digitalObjectsGroupName = FILE_GROUP_OBJECTS.toString(); + final var dataResolverEndpoint = DataResolver.getDefaultEndpoint(); + final var counter = UpdateCounts.counter(); + final var guard = new Object(); + + final BiFunction<MetsType.FileSec, String, MetsType.FileSec.FileGrp> fgfinder = (fsec, fgname) -> { + final var fgroups = (fsec != null) ? fsec.getFileGrp() : Collections.<MetsType.FileSec.FileGrp>emptyList(); + for (final var fgroup : fgroups) { + if (fgname.equals(fgroup.getUSE())) + return fgroup; + } + + return null; + }; + + final Consumer<String> fixer = (objectId) -> { + try { + final var mets = this.loadMetsData(objectId) + .getMets(); + + final var fsec = mets.getFileSec(); + if (fsec == null) + return; + + final var fgroup = fgfinder.apply(fsec, digitalObjectsGroupName); + if (fgroup == null) + return; + + final var updatemsgs = new ArrayList<String>(); + FileCollection description = null; + + final var files = fgroup.getFile(); + for (final var fit = files.iterator(); fit.hasNext();) { + final var flocations = fit.next().getFLocat(); + for (final var flit = flocations.iterator(); flit.hasNext(); ) { + final var flocat = flit.next(); + final var oldurl = flocat.getHref(); + + // should the url be fixed? + if (!oldurl.startsWith(dataResolverEndpoint)) + continue; // no, skip this file + + final var filename = flocat.getTitle(); + if (filename == null) + throw new IllegalStateException("Filename is missing!"); + + // generate description from storage layout + if (description == null) + description = this.describe(objectId); + + // find current file's url by its filename... + String newurl = null; + for (final var fce : description.files) { + final var fceurl = fce.getUrl(); + if (fceurl.endsWith(filename)) { + newurl = fceurl; + break; + } + } + + if (newurl == null) + throw new IllegalStateException("No matching URL found for file '" + filename + "'!"); + + flocat.setHref(newurl); + updatemsgs.add("FLocat-URL: " + oldurl + " -> " + flocat.getHref()); + } + } + + if (updatemsgs.isEmpty()) + return; + + synchronized (guard) { + log.info("Updates for object '" + objectId + "':"); + for (final var msg : updatemsgs) + log.info(" " + msg); + } + + this.writeMetsFile(mets); + counter.increment(UpdateCounts.UPDATED); + } + catch (Exception error) { + log.log(Level.WARNING, "Fixing object '" + objectId + "' failed!", error); + counter.increment(UpdateCounts.FAILED); + } + }; + + log.info("Fixing object file-locations in archive '" + this.getName() + "'..."); + try (final var ids = this.getObjectIds()) { + ParallelProcessors.consumer(fixer) + .consume(ids, ObjectArchiveSingleton.executor()); + } + + final var numFixed = counter.get(UpdateCounts.UPDATED); + final var numFailed = counter.get(UpdateCounts.FAILED); + log.info("Fixed " + numFixed + " object(s), failed " + numFailed); + if (!MigrationUtils.acceptable(numFixed + numFailed, numFailed, MigrationUtils.getFailureRate(mc))) + throw new BWFLAException("Fixing object file-locations failed!"); + } } -- GitLab