diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Checkpoints.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Checkpoints.java index bb4cbd3..fd54f00 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Checkpoints.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Checkpoints.java @@ -50,13 +50,13 @@ class Checkpoints { private static final Logger LOG = LoggerFactory.getLogger(Checkpoints.class); - private static final String ID = "checkpoint"; + static final String ID = "checkpoint"; /** * Property name to store all checkpoint data. The data is either stored as * Revision => expiryTime or Revision => JSON with expiryTime and info. */ - private static final String PROP_CHECKPOINT = "data"; + static final String PROP_CHECKPOINT = "data"; /** * Number of create calls after which old expired checkpoints entries would @@ -251,7 +251,7 @@ class Checkpoints { private final Map info; - private Info(long expiryTime, + Info(long expiryTime, @Nullable RevisionVector checkpoint, @Nonnull Map info) { this.expiryTime = expiryTime; diff --git a/oak-run/README.md b/oak-run/README.md index 288e2e1..87f35d3 100644 --- a/oak-run/README.md +++ b/oak-run/README.md @@ -156,6 +156,8 @@ The 'list' option (treated as a default when nothing is specified) will list all The 'rm-all' option will wipe clean the 'checkpoints' node. The 'rm-unreferenced' option will remove all checkpoints except the one referenced from the async indexer (/:async@async). The 'rm ' option will remove a specific checkpoint from the repository. +The 'info ' option will return metadata information for the given checkpoint. +The 'set \[\]' option will set the metadata property. If the value is omitted, the property will be removed. Tika diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/Checkpoints.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/Checkpoints.java index 736e59e..2c3b00f 100644 --- a/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/Checkpoints.java +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/Checkpoints.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; @@ -75,6 +76,26 @@ public abstract class Checkpoints { */ public abstract int remove(String cp); + /** + * Return checkpoint metadata + * + * @param cp a checkpoint string. + * @return checkpoints metadata map or null if checkpoint can't be found + */ + public abstract Map getInfo(String cp); + + /** + * Set the property in the checkpoint metadata. + * + * @param cp a checkpoint string. + * @param name property name + * @param value new value of the property. the property will be removed if the value is {@code null} + * @return {@code 1} if the checkpoint was successfully remove, {@code 0} if + * there is no such checkpoint or {@code -1} if the operation did + * not succeed. + */ + public abstract int setInfoProperty(String cp, String name, String value); + @Nonnull static Set getReferencedCheckpoints(NodeState root) { Set cps = new HashSet(); diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/DocumentCheckpoints.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/DocumentCheckpoints.java index 4b139f7..825e0e9 100644 --- a/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/DocumentCheckpoints.java +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/DocumentCheckpoints.java @@ -73,4 +73,26 @@ class DocumentCheckpoints extends Checkpoints { return CheckpointsHelper.remove(store, r); } + @Override + public Map getInfo(String cp) { + Revision r; + try { + r = Revision.fromString(cp); + } catch (IllegalArgumentException e) { + return null; + } + return CheckpointsHelper.getInfo(store, r); + } + + @Override + public int setInfoProperty(String cp, String name, String value) { + Revision r; + try { + r = Revision.fromString(cp); + } catch (IllegalArgumentException e) { + return 0; + } + return CheckpointsHelper.setInfoProperty(store, r, name, value); + } + } diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/SegmentTarCheckpoints.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/SegmentTarCheckpoints.java index 97dab9b..bb14c0e 100644 --- a/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/SegmentTarCheckpoints.java +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/checkpoint/SegmentTarCheckpoints.java @@ -21,11 +21,15 @@ import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreB import java.io.File; import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import com.google.common.collect.Lists; import com.google.common.io.Closer; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.segment.SegmentNodeState; import org.apache.jackrabbit.oak.segment.file.FileStore; import org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException; @@ -119,6 +123,43 @@ class SegmentTarCheckpoints extends Checkpoints { } } + @Override + public Map getInfo(String cp) { + SegmentNodeState head = store.getHead(); + NodeState props = head.getChildNode("checkpoints").getChildNode(cp).getChildNode("properties"); + if (props.exists()) { + Map info = new HashMap<>(); + for (PropertyState p : props.getProperties()) { + info.put(p.getName(), p.getValue(Type.STRING)); + } + return info; + } else { + return null; + } + } + + @Override + public int setInfoProperty(String cp, String name, String value) { + SegmentNodeState head = store.getHead(); + NodeBuilder builder = head.builder(); + + NodeBuilder props = builder.getChildNode("checkpoints").getChildNode(cp).getChildNode("properties"); + if (props.exists()) { + if (value == null) { + props.removeProperty(name); + } else { + props.setProperty(name, value, Type.STRING); + } + if (store.getRevisions().setHead(head.getRecordId(), asSegmentNodeState(builder).getRecordId())) { + return 1; + } else { + return -1; + } + } else { + return 0; + } + } + private static SegmentNodeState asSegmentNodeState(NodeBuilder builder) { return (SegmentNodeState) builder.getNodeState(); } diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/CheckpointsHelper.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/CheckpointsHelper.java index 2f31f41..52559b6 100644 --- a/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/CheckpointsHelper.java +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/CheckpointsHelper.java @@ -18,6 +18,8 @@ */ package org.apache.jackrabbit.oak.plugins.document; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.SortedMap; @@ -25,6 +27,8 @@ import java.util.SortedMap; import com.google.common.collect.Maps; import static org.apache.jackrabbit.oak.plugins.document.Checkpoints.Info; +import static org.apache.jackrabbit.oak.plugins.document.Checkpoints.PROP_CHECKPOINT; + import org.apache.jackrabbit.oak.plugins.document.util.Utils; /** @@ -82,4 +86,39 @@ public abstract class CheckpointsHelper { return r; } + public static Map getInfo(DocumentNodeStore store, Revision r) { + Info info = store.getCheckpoints().getCheckpoints().get(r); + if (info == null) { + return null; + } + return info.get(); + } + + public static int setInfoProperty(DocumentNodeStore store, Revision r, String name, String value) { + DocumentStore docStore = store.getDocumentStore(); + Document cdoc = docStore.find(Collection.SETTINGS, Checkpoints.ID, 0); + SortedMap data = null; + if (cdoc != null) { + data = (SortedMap) cdoc.get(PROP_CHECKPOINT); + } + Info info = null; + if (data != null) { + info = Info.fromString(data.get(r)); + } + if (info == null) { + return 0; + } + Map metadata = new LinkedHashMap<>(info.get()); + if (value == null) { + metadata.remove(name); + } else { + metadata.put(name, value); + } + Info newInfo = new Info(info.getExpiryTime(), info.getCheckpoint(), metadata); + + UpdateOp op = new UpdateOp(Checkpoints.ID, false); + op.setMapEntry(PROP_CHECKPOINT, r, newInfo.toString()); + docStore.createOrUpdate(Collection.SETTINGS, op); + return 1; + } } diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/run/CheckpointsCommand.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/run/CheckpointsCommand.java index b9931cb..4bf5025 100644 --- a/oak-run/src/main/java/org/apache/jackrabbit/oak/run/CheckpointsCommand.java +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/run/CheckpointsCommand.java @@ -19,6 +19,8 @@ package org.apache.jackrabbit.oak.run; import java.io.File; import java.sql.Timestamp; +import java.util.List; +import java.util.Map; import org.apache.jackrabbit.oak.checkpoint.Checkpoints; import org.apache.jackrabbit.oak.plugins.document.DocumentMK; @@ -40,7 +42,7 @@ class CheckpointsCommand implements Command { OptionSet options = parser.parse(args); if (options.nonOptionArguments().isEmpty()) { - System.out.println("usage: checkpoints {|} [list|rm-all|rm-unreferenced|rm ] [--segment]"); + System.out.println("usage: checkpoints {|} [list|rm-all|rm-unreferenced|rm |info |set []] [--segment]"); System.exit(1); } @@ -51,7 +53,7 @@ class CheckpointsCommand implements Command { String op = "list"; if (options.nonOptionArguments().size() >= 2) { op = options.nonOptionArguments().get(1).toString(); - if (!"list".equals(op) && !"rm-all".equals(op) && !"rm-unreferenced".equals(op) && !"rm".equals(op)) { + if (!"list".equals(op) && !"rm-all".equals(op) && !"rm-unreferenced".equals(op) && !"rm".equals(op) && !"info".equals(op) && !"set".equals(op)) { failWith("Unknown command."); } } @@ -117,6 +119,45 @@ class CheckpointsCommand implements Command { failWith("Checkpoint '" + cp + "' not found."); } } + } else if ("info".equals(op)) { + if (options.nonOptionArguments().size() < 3) { + failWith("Missing checkpoint id"); + } else { + String cp = options.nonOptionArguments().get(2).toString(); + Map info = cps.getInfo(cp); + if (info != null) { + for (Map.Entry e : info.entrySet()) { + System.out.println(e.getKey() + '\t' + e.getValue()); + } + } else { + failWith("Checkpoint '" + cp + "' not found."); + } + } + } else if ("set".equals(op)) { + if (options.nonOptionArguments().size() < 4) { + failWith("Missing checkpoint id"); + } else { + List l = options.nonOptionArguments(); + String cp = l.get(2).toString(); + String name = l.get(3).toString(); + String value = null; + if (l.size() >= 5) { + value = l.get(4).toString(); + } + long time = System.currentTimeMillis(); + int cnt = cps.setInfoProperty(cp, name, value); + time = System.currentTimeMillis() - time; + if (cnt != 0) { + if (cnt == 1) { + System.out.println("Updated checkpoint " + cp + " in " + + time + "ms."); + } else { + failWith("Failed to remove checkpoint " + cp); + } + } else { + failWith("Checkpoint '" + cp + "' not found."); + } + } } success = true; } catch (Throwable t) {