Skip to content

Commit 360ce0b

Browse files
committed
MCR-3126 improve ocfl
* add restore derivate command * add describe object command * fix MCRVersionedPath#getParent does not return a versioned path
1 parent 71dc5f9 commit 360ce0b

File tree

10 files changed

+356
-64
lines changed

10 files changed

+356
-64
lines changed

mycore-base/src/main/java/org/mycore/datamodel/niofs/MCRVersionedFileSystem.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
package org.mycore.datamodel.niofs;
2020

21+
import java.nio.file.FileSystemException;
22+
import java.nio.file.NoSuchFileException;
23+
2124
/**
2225
* Represents a file system with versioning capabilities. This class extends {@link MCRAbstractFileSystem}
2326
* to work with {@link MCRVersionedFileSystemProvider}, thereby enabling handling of paths that
@@ -45,4 +48,16 @@ public MCRVersionedFileSystemProvider provider() {
4548
return (MCRVersionedFileSystemProvider) super.provider();
4649
}
4750

51+
/**
52+
* Restores the root with the given version.
53+
* <p>
54+
* After calling this method, the implementing FileSystem should be ready to accept data for this root.
55+
*
56+
* @param owner ,e.g. derivate ID
57+
* @param version the version
58+
* @throws FileSystemException if restoring the root directory fails
59+
* @throws NoSuchFileException more specific, if the owner or version never existed
60+
*/
61+
public abstract void restoreRoot(String owner, String version) throws FileSystemException, NoSuchFileException;
62+
4863
}

mycore-base/src/main/java/org/mycore/datamodel/niofs/MCRVersionedPath.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,29 @@ public String getVersion() {
7676
return ownerVersion.version();
7777
}
7878

79+
/**
80+
* {@inheritDoc}
81+
*/
7982
@Override
8083
public MCRVersionedPath getParent() {
8184
MCRPath parent = super.getParent();
8285
return parent != null ? ofPath(parent) : null;
8386
}
8487

88+
/**
89+
* {@inheritDoc}
90+
*/
91+
@Override
92+
public MCRPath getRoot() {
93+
if (!isAbsolute()) {
94+
return null;
95+
}
96+
if (getNameCount() == 0) {
97+
return this;
98+
}
99+
return getFileSystem().provider().getPath(this.getOwner(), this.getVersion(), SEPARATOR_STRING);
100+
}
101+
85102
@Override
86103
public MCRVersionedPath subpath(int beginIndex, int endIndex) {
87104
return ofPath(super.subpath(beginIndex, endIndex));
@@ -99,7 +116,12 @@ public MCRVersionedPath getName(int index) {
99116

100117
@Override
101118
protected MCRVersionedPath getPath(String owner, String path, MCRAbstractFileSystem fs) {
102-
return ofPath(super.getPath(owner, path, fs));
119+
MCRVersionedFileSystemProvider provider = getFileSystem().provider();
120+
if (owner == null) {
121+
return provider.getPath(null, path);
122+
} else {
123+
return provider.getPath(owner, this.getVersion(), path);
124+
}
103125
}
104126

105127
public String toRelativePath() {

mycore-ocfl/src/main/java/org/mycore/ocfl/commands/MCROCFLCommands.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444

4545
import org.apache.logging.log4j.LogManager;
4646
import org.apache.logging.log4j.Logger;
47+
import org.jdom2.Document;
4748
import org.jdom2.JDOMException;
49+
import org.mycore.access.MCRAccessException;
4850
import org.mycore.common.MCRUsageException;
4951
import org.mycore.common.config.MCRConfiguration2;
5052
import org.mycore.common.config.MCRConfigurationDir;
@@ -59,6 +61,8 @@
5961
import org.mycore.datamodel.classifications2.utils.MCRXMLTransformer;
6062
import org.mycore.datamodel.common.MCRAbstractMetadataVersion;
6163
import org.mycore.datamodel.common.MCRXMLMetadataManager;
64+
import org.mycore.datamodel.metadata.MCRDerivate;
65+
import org.mycore.datamodel.metadata.MCRMetadataManager;
6266
import org.mycore.datamodel.metadata.MCRObjectID;
6367
import org.mycore.datamodel.niofs.MCRFileAttributes;
6468
import org.mycore.datamodel.niofs.MCRPath;
@@ -72,17 +76,22 @@
7276
import org.mycore.ocfl.metadata.migration.MCROCFLMigration;
7377
import org.mycore.ocfl.metadata.migration.MCROCFLRevisionPruner;
7478
import org.mycore.ocfl.niofs.MCROCFLFileSystemProvider;
79+
import org.mycore.ocfl.repository.MCROCFLRepository;
7580
import org.mycore.ocfl.repository.MCROCFLRepositoryProvider;
7681
import org.mycore.ocfl.user.MCROCFLXMLUserManager;
7782
import org.mycore.ocfl.util.MCROCFLObjectIDPrefixHelper;
7883
import org.mycore.user2.MCRUser;
7984
import org.mycore.user2.MCRUserManager;
8085
import org.xml.sax.SAXException;
8186

87+
import com.fasterxml.jackson.databind.ObjectMapper;
88+
import com.fasterxml.jackson.databind.SerializationFeature;
89+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
8290
import com.google.common.collect.MapDifference;
8391
import com.google.common.collect.Maps;
8492

8593
import io.ocfl.api.OcflRepository;
94+
import io.ocfl.api.model.ObjectDetails;
8695

8796
@SuppressWarnings("JavaUtilDate")
8897
@MCRCommandGroup(name = "OCFL Commands")
@@ -153,6 +162,27 @@ protected static void migrateWithPrunersAndRepositoryKeyOrMetadataManager(String
153162
SUCCESS_BUT_WITHOUT_HISTORY + ": " + withoutHistory.size() + ls);
154163
}
155164

165+
@MCRCommand(syntax = "describe ocfl object {0} of repository {1}",
166+
help = "Prints all of the details about an object and all of its versions."
167+
+ " It is required to add the ocfl prefix like 'mcrobject:' or 'mcracl:' to the object id.")
168+
public static void describeObject(String objectId, String repositoryId) {
169+
repositoryId = (repositoryId == null || repositoryId.isBlank()) ? "Main" : repositoryId;
170+
MCROCFLRepositoryProvider provider = MCROCFLRepositoryProvider.getProvider(repositoryId);
171+
MCROCFLRepository repository = provider.getRepository();
172+
ObjectDetails objectDetails = repository.describeObject(objectId);
173+
174+
ObjectMapper mapper = new ObjectMapper();
175+
mapper.registerModule(new JavaTimeModule());
176+
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
177+
mapper.enable(SerializationFeature.INDENT_OUTPUT);
178+
try {
179+
String pretty = mapper.writeValueAsString(objectDetails);
180+
LOGGER.info("\n{}", pretty);
181+
} catch (Exception e) {
182+
LOGGER.error("Failed to pretty-print ObjectDetails", e);
183+
}
184+
}
185+
156186
@MCRCommand(syntax = "migrate metadata to metadatamanager {1} and pruners {2} ",
157187
help = "migrates all the metadata to the ocfl " +
158188
"repository with the id {0} and prunes the revisions with the given pruners",
@@ -304,6 +334,21 @@ public static void restoreObjFromOCFLVersioned(String mcridString, String revisi
304334
}
305335
}
306336

337+
@MCRCommand(syntax = "restore derivate {0} from ocfl with version {1}",
338+
help = "restore derivate {0} with version {1} to current store from ocfl history")
339+
public static List<String> restoreDerivateFromOCFL(String derivateId, String revision)
340+
throws IOException, JDOMException, MCRAccessException {
341+
// restore ocfl content
342+
MCROCFLFileSystemProvider.get().getFileSystem().restoreRoot(derivateId, revision);
343+
// update metadata
344+
MCRObjectID mcrDerivateId = MCRObjectID.getInstance(derivateId);
345+
Document derivateXml = MCRXMLMetadataManager.getInstance().retrieveXML(mcrDerivateId);
346+
MCRDerivate mcrDerivate = new MCRDerivate(derivateXml);
347+
MCRMetadataManager.update(mcrDerivate);
348+
// tile
349+
return List.of("tile images of derivate " + derivateId);
350+
}
351+
307352
@MCRCommand(syntax = "restore object {0} from ocfl",
308353
help = "restore latest mcrobject {0} to current store from ocfl history")
309354
public static void restoreObjFromOCFL(String mcridString) throws IOException {
@@ -498,7 +543,7 @@ public static void validateDerivate(String derivateId) throws IOException {
498543

499544
private static void logConfirm(String type) {
500545
LOGGER.info(() -> String.format(Locale.ROOT, """
501-
546+
502547
\u001B[93mEnter the command again to confirm \u001B[4mPERMANENTLY\u001B[24m deleting ALL\
503548
hidden/archived OCFL %s.\u001B[0m
504549
\u001B[41mTHIS ACTION CANNOT BE UNDONE!\u001B[0m""", type));

mycore-ocfl/src/main/java/org/mycore/ocfl/niofs/MCROCFLFileSystem.java

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,21 @@
1818

1919
package org.mycore.ocfl.niofs;
2020

21+
import static org.mycore.ocfl.util.MCROCFLVersionHelper.MESSAGE_UPDATED;
22+
2123
import java.io.IOException;
2224
import java.nio.file.FileAlreadyExistsException;
2325
import java.nio.file.FileStore;
2426
import java.nio.file.FileSystemException;
27+
import java.nio.file.NoSuchFileException;
2528
import java.nio.file.Path;
2629
import java.util.Collection;
2730
import java.util.Collections;
2831
import java.util.List;
2932
import java.util.stream.Stream;
3033

34+
import io.ocfl.api.model.ObjectVersionId;
35+
import io.ocfl.api.model.VersionInfo;
3136
import org.mycore.datamodel.metadata.MCRObjectID;
3237
import org.mycore.datamodel.niofs.MCRVersionedFileSystem;
3338
import org.mycore.ocfl.repository.MCROCFLRepository;
@@ -79,8 +84,8 @@ public void createRoot(String owner) throws FileSystemException {
7984
try {
8085
virtualObject.create();
8186
} catch (IOException ioException) {
82-
FileSystemException fileSystemException
83-
= new FileSystemException(null, null, "Cannot create root of '" + owner + "'.");
87+
FileSystemException fileSystemException =
88+
new FileSystemException(null, null, "Cannot create root of '" + owner + "'.");
8489
fileSystemException.initCause(ioException);
8590
throw fileSystemException;
8691
}
@@ -102,13 +107,37 @@ public void removeRoot(String owner) throws FileSystemException {
102107
try {
103108
virtualObject.purge();
104109
} catch (IOException ioException) {
105-
FileSystemException fileSystemException
106-
= new FileSystemException(null, null, "Cannot remove root of '" + owner + "'.");
110+
FileSystemException fileSystemException =
111+
new FileSystemException(null, null, "Cannot remove root of '" + owner + "'.");
107112
fileSystemException.initCause(ioException);
108113
throw fileSystemException;
109114
}
110115
}
111116

117+
/**
118+
* {@inheritDoc}
119+
* <p>
120+
* Be aware that this OCFL implementation works outside the {@link MCROCFLFileSystemTransaction}.
121+
*/
122+
@Override
123+
public void restoreRoot(String owner, String version) throws FileSystemException {
124+
MCROCFLVirtualObjectProvider virtualObjectProvider = provider().virtualObjectProvider();
125+
if (!virtualObjectProvider.exists(owner, version)) {
126+
throw new NoSuchFileException(null, null, "No such version '" + version + "' for root '" + owner + "'.");
127+
}
128+
try {
129+
MCROCFLRepository repository = provider().getRepository();
130+
String ocflObjectId = MCROCFLObjectIDPrefixHelper.toDerivateObjectId(owner);
131+
repository.replicateVersionAsHead(ObjectVersionId.version(ocflObjectId, version),
132+
new VersionInfo().setMessage(MESSAGE_UPDATED));
133+
} catch (Exception exc) {
134+
FileSystemException fileSystemException =
135+
new FileSystemException(null, null, "Unable to restore '" + owner + "' to version '" + version + "'.");
136+
fileSystemException.initCause(exc);
137+
throw fileSystemException;
138+
}
139+
}
140+
112141
/**
113142
* Returns an iterable over the root directories in the OCFL file system.
114143
*

mycore-ocfl/src/main/java/org/mycore/ocfl/niofs/MCROCFLVirtualObjectProvider.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,25 +182,44 @@ private MCROCFLVirtualObject getWritable(ObjectVersionId id) {
182182
* @return {@code true} if the owner exists, {@code false} otherwise.
183183
*/
184184
public boolean exists(String owner) {
185-
ObjectVersionId head = toObjectVersionId(owner, null);
186-
if (this.readMap.containsKey(head)) {
185+
return exists(owner, null);
186+
}
187+
188+
/**
189+
* Checks if the specified owner and version exists in the repository.
190+
* <p>
191+
* This also will return true if the owner was marked for purge or was deleted (and not purged),
192+
* because the object still exists in the OCFL repository.
193+
*
194+
* @param owner the owner of the object.
195+
* @param version the version of the object
196+
* @return {@code true} if the owner and version exists, {@code false} otherwise.
197+
*/
198+
public boolean exists(String owner, String version) {
199+
ObjectVersionId objectVersionId = toObjectVersionId(owner, version);
200+
if (this.readMap.containsKey(objectVersionId)) {
187201
return true;
188202
}
189203
boolean isActive = MCROCFLFileSystemTransaction.isActive();
190204
Long transactionId = MCROCFLFileSystemTransaction.getTransactionId();
191205
if (isActive && this.writeMap.containsKey(transactionId)) {
192-
MCROCFLVirtualObject headVirtualObject = this.writeMap.get(transactionId).get(head);
206+
MCROCFLVirtualObject headVirtualObject = this.writeMap.get(transactionId).get(objectVersionId);
193207
if (headVirtualObject != null) {
194208
return headVirtualObject.isMarkedForCreate();
209+
195210
}
196211
}
197-
if (!repository.containsObject(head.getObjectId())) {
212+
if (!repository.containsObject(objectVersionId.getObjectId())) {
213+
return false;
214+
}
215+
try {
216+
OcflObjectVersion object = repository.getObject(objectVersionId);
217+
return object.getFiles().stream()
218+
.map(OcflObjectVersionFile::getPath)
219+
.anyMatch(path -> path.startsWith(MCROCFLVirtualObject.FILES_DIRECTORY));
220+
} catch(NotFoundException ignore) {
198221
return false;
199222
}
200-
OcflObjectVersion object = repository.getObject(head);
201-
return object.getFiles().stream()
202-
.map(OcflObjectVersionFile::getPath)
203-
.anyMatch(path -> path.startsWith(MCROCFLVirtualObject.FILES_DIRECTORY));
204223
}
205224

206225
/**

mycore-ocfl/src/test/java/org/mycore/ocfl/MCROCFLTestCaseHelper.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
import java.nio.file.SimpleFileVisitor;
2929
import java.nio.file.attribute.BasicFileAttributes;
3030

31+
import org.mycore.datamodel.metadata.MCRDerivate;
32+
import org.mycore.datamodel.metadata.MCRMetaIFS;
33+
import org.mycore.datamodel.metadata.MCRMetaLinkID;
34+
import org.mycore.datamodel.metadata.MCRObject;
35+
import org.mycore.datamodel.metadata.MCRObjectID;
3136
import org.mycore.datamodel.niofs.MCRVersionedPath;
3237
import org.mycore.ocfl.repository.MCROCFLRepository;
3338
import org.mycore.ocfl.util.MCROCFLObjectIDPrefixHelper;
@@ -74,6 +79,25 @@ public static void loadDerivate(String derivateId) throws URISyntaxException, IO
7479
Files.walkFileTree(sourcePath, new CopyFileVisitor(targetPath));
7580
}
7681

82+
public static MCRObject createObject(String objectId) {
83+
MCRObject object = new MCRObject();
84+
object.setId(MCRObjectID.getInstance(objectId));
85+
object.setSchema("noSchema");
86+
return object;
87+
}
88+
89+
public static MCRDerivate createDerivate(String objectId, String derivateId) {
90+
MCRDerivate derivate = new MCRDerivate();
91+
derivate.setId(MCRObjectID.getInstance(derivateId));
92+
derivate.setSchema("datamodel-derivate.xsd");
93+
MCRMetaIFS ifs = new MCRMetaIFS("internal", null);
94+
derivate.getDerivate().setInternals(ifs);
95+
MCRMetaLinkID mcrMetaLinkID = new MCRMetaLinkID("linkmeta", 0);
96+
mcrMetaLinkID.setReference(objectId, null, null);
97+
derivate.getDerivate().setLinkMeta(mcrMetaLinkID);
98+
return derivate;
99+
}
100+
77101
private static class CopyFileVisitor extends SimpleFileVisitor<Path> {
78102

79103
private final Path targetPath;

0 commit comments

Comments
 (0)