From 1fad8ded923e6b537edccd260f08e0a9f4f1135d Mon Sep 17 00:00:00 2001 From: Marc Hess Date: Thu, 23 May 2024 16:22:29 -0400 Subject: [PATCH] add countNodes and rmNodes as native scripts since their usage is ubiquitous across AEM --- .../oak/console/GroovyConsole.groovy | 4 +- .../console/commands/CountNodesCommand.groovy | 86 +++++++++++++++++++ .../commands/CountNodesCommand.properties | 21 +++++ .../console/commands/RemoveNodeCommand.groovy | 58 +++++++++++++ .../commands/RemoveNodeCommand.properties | 21 +++++ 5 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/CountNodesCommand.groovy create mode 100644 oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/CountNodesCommand.properties create mode 100644 oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/RemoveNodeCommand.groovy create mode 100644 oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/RemoveNodeCommand.properties diff --git a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/GroovyConsole.groovy b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/GroovyConsole.groovy index 904ab15a609..b1563ac2f79 100644 --- a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/GroovyConsole.groovy +++ b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/GroovyConsole.groovy @@ -128,7 +128,9 @@ class GroovyConsole { new RetrieveCommand(shell), new LuceneCommand(shell), new ExportRelevantDocumentsCommand(shell), - new ExportCommand(shell) + new ExportCommand(shell), + new CountNodesCommand(shell), + new RemoveNodeCommand(shell) ]) if(session.store instanceof DocumentNodeStore){ diff --git a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/CountNodesCommand.groovy b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/CountNodesCommand.groovy new file mode 100644 index 00000000000..fdb9ab29c1f --- /dev/null +++ b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/CountNodesCommand.groovy @@ -0,0 +1,86 @@ +import groovy.transform.CompileStatic; +import org.apache.jackrabbit.oak.api.Blob; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.segment.SegmentBlob; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStore; +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; +import org.codehaus.groovy.tools.shell.CommandSupport; +import org.apache.jackrabbit.oak.console.ConsoleSession +import org.codehaus.groovy.tools.shell.Groovysh; + +import java.util.concurrent.atomic.AtomicInteger; + +@CompileStatic +class CountNodesCommand extends CommandSupport { + static final String COMMAND_NAME = 'count-nodes'; + + CountNodesCommand(Groovysh shell) { + super(shell, COMMAND_NAME, 'countNodes'); + } + + @Override + Object execute(List args) { + NodeStore store = getSession().getStore(); + NodeState rootState = store.getRoot(); + + AtomicInteger count = new AtomicInteger(0); + AtomicInteger binaries = new AtomicInteger(0); + + countNodes(rootState, "/", 1000000, 1000, count, binaries, true); + + io.out.println("Total nodes: " + count.get()); + io.out.println("Total binaries: " + binaries.get()); + + return null; + } + + void countNodes(NodeState n, String path, Integer flush, Long warnAt, AtomicInteger count, AtomicInteger binaries, boolean root) { + if (root) { + io.out.println("Counting nodes in tree " + path); + } + + int cnt = count.incrementAndGet(); + if (cnt % flush == 0) { + io.out.println(" " + cnt); + } + + try { + for (PropertyState prop : (Iterable) n.getProperties()) { + if (prop.getType() == Type.BINARY || prop.getType() == Type.BINARIES) { + for (Blob b : prop.getValue(Type.BINARIES)) { + binaries.incrementAndGet(); + if (b instanceof SegmentBlob) { + if (!((SegmentBlob) b).isExternal()) { + b.length(); + } + } else { + b.length(); + } + } + } + } + + long kids = n.getChildNodeCount(warnAt); + if (kids >= warnAt) { + io.out.println(path + " has " + kids + " child nodes"); + } + + for (ChildNodeEntry child : (Iterable) n.getChildNodeEntries()) { + countNodes(child.getNodeState(), path + child.getName() + "/", flush, warnAt, count, binaries, false); + } + } catch (Exception e) { + io.out.println("warning unable to read node " + path); + } + + if (root) { + io.out.println("Total nodes in tree " + path + ": " + cnt); + io.out.println("Total binaries in tree " + path + ": " + binaries.get()); + } + } + + ConsoleSession getSession() { + return (ConsoleSession) variables.get("session"); + } +} \ No newline at end of file diff --git a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/CountNodesCommand.properties b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/CountNodesCommand.properties new file mode 100644 index 00000000000..59857a8a187 --- /dev/null +++ b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/CountNodesCommand.properties @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +command.description=Walks the repository counting nodes and logging node paths that throw SNFE. +command.usage= +command.help=Walks the repository counting nodes and logging node paths that throw SNFE. \ No newline at end of file diff --git a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/RemoveNodeCommand.groovy b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/RemoveNodeCommand.groovy new file mode 100644 index 00000000000..f5a9df7ebed --- /dev/null +++ b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/RemoveNodeCommand.groovy @@ -0,0 +1,58 @@ +import org.apache.jackrabbit.oak.spi.commit.CommitInfo +import org.apache.jackrabbit.oak.spi.commit.EmptyHook +import org.apache.jackrabbit.oak.commons.PathUtils +import org.apache.jackrabbit.oak.console.ConsoleSession +import org.apache.jackrabbit.oak.spi.state.NodeStore +import org.codehaus.groovy.tools.shell.CommandSupport +import org.codehaus.groovy.tools.shell.Groovysh +import groovy.transform.CompileStatic + +@CompileStatic +class RemoveNodeCommand extends CommandSupport { + static final String COMMAND_NAME = 'remove-node' + + RemoveNodeCommand(Groovysh shell) { + super(shell, COMMAND_NAME, "rmNode") + } + + @Override + Object execute(List args) { + if (args.isEmpty()) { + throw new IllegalArgumentException("Usage: rmNode ") + } + + String path = args[0] + ConsoleSession session = getSession() + NodeStore nodeStore = session.getStore() + boolean result = removeNode(nodeStore, path) + + if (result) { + io.out.println("Node at path '${path}' removed successfully.") + } else { + io.out.println("Node at path '${path}' does not exist or could not be removed.") + } + + return null + } + + private boolean removeNode(NodeStore nodeStore, String path) { + def rootBuilder = nodeStore.root.builder() + def targetBuilder = rootBuilder + + PathUtils.elements(path).each { element -> + targetBuilder = targetBuilder.getChildNode(element) + } + + if (targetBuilder.exists()) { + targetBuilder.remove() + nodeStore.merge(rootBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY) + return true + } else { + return false + } + } + + private ConsoleSession getSession() { + return (ConsoleSession) variables.get("session"); + } +} \ No newline at end of file diff --git a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/RemoveNodeCommand.properties b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/RemoveNodeCommand.properties new file mode 100644 index 00000000000..67ec8fae6d7 --- /dev/null +++ b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/RemoveNodeCommand.properties @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +command.description=Removes a node, and its subtree, from the repository. +command.usage= +command.help=Removes a node, and its subtree, from the repository \ No newline at end of file