Author: stefanegli
Date: Thu Jul 16 10:04:05 2020
New Revision: 1879938

URL: http://svn.apache.org/viewvc?rev=1879938&view=rev
Log:
OAK-9131 : introducing scanners for nt:frozenNode references

Added:
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRef.java
   (with props)
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsByScanningCommand.java
   (with props)
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsUsingIndexCommand.java
   (with props)
    
jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/FrozenNodeReferenceCreator.java
   (with props)
Modified:
    jackrabbit/oak/trunk/oak-run/README.md
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Utils.java

Modified: jackrabbit/oak/trunk/oak-run/README.md
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/README.md?rev=1879938&r1=1879937&r2=1879938&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-run/README.md (original)
+++ jackrabbit/oak/trunk/oak-run/README.md Thu Jul 16 10:04:05 2020
@@ -17,6 +17,8 @@ The following runmodes are currently ava
     * debug           : Print status information about an Oak repository
     * explore         : Starts a GUI browser based on java swing
     * export          : Export repository content as json
+    * frozennoderefsbyscanning : Scan for nt:frozenNode references via query
+    * frozennoderefsusingindex : Scan for nt:frozenNode references via 
/oak:index
     * garbage         : Identifies blob garbage on a DocumentMK repository
     * help            : Print a list of available runmodes
     * history         : Trace the history of a node
@@ -136,6 +138,42 @@ browsing of an existing oak repository.
 
     $ java -jar oak-run-*.jar explore /path/to/oak/repository [skip-size-check]
 
+frozennoderefsbyscanning
+------------------------
+
+This command executes a potentially expensive (!) traversing query searching 
for
+all properties formatted as a UUID (incl String, Reference types) and verifies
+if they represent (potential) references to nt:frozenNode.
+
+Since this is a rather expensive command, consider using 
frozennoderefsusingindex
+(at least first) instead.
+
+If this is used eg on a MongoDB, consider running the command against
+a secondary MongoDB node, such as to not overload the primary MongoDB node.
+
+This tool is part of the effort to change the default nt:frozenNode node type
+definition to no longer be a mix:referenceable (see OAK-9134). Even though
+existing definitions aren't modified, the tool can be used to verify if
+an existing repository would potentially be in violation of OAK-9134 - ie if
+there are existing use cases of nt:frozenNode being a mix:referenceable.
+
+
+frozennoderefsusingindex
+------------------------
+
+This command browses through /oak:index/references and verifies if they
+represent references to nt:frozenNode.
+
+If this is used eg on a MongoDB, consider running the command against
+a secondary MongoDB node, such as to not overload the primary MongoDB node.
+
+This tool is part of the effort to change the default nt:frozenNode node type
+definition to no longer be a mix:referenceable (see OAK-9134). Even though
+existing definitions aren't modified, the tool can be used to verify if
+an existing repository would potentially be in violation of OAK-9134 - ie if
+there are existing use cases of nt:frozenNode being a mix:referenceable.
+
+
 History
 -------
 

Modified: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java?rev=1879938&r1=1879937&r2=1879938&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java
 Thu Jul 16 10:04:05 2020
@@ -42,6 +42,8 @@ public final class AvailableModes {
             .put("debug", new DebugCommand())
             .put("explore", new ExploreCommand())
             .put(NodeStateExportCommand.NAME, new NodeStateExportCommand())
+            .put(FrozenNodeRefsByScanningCommand.NAME, new 
FrozenNodeRefsByScanningCommand())
+            .put(FrozenNodeRefsUsingIndexCommand.NAME, new 
FrozenNodeRefsUsingIndexCommand())
             .put("garbage", new GarbageCommand())
             .put("help", new HelpCommand())
             .put("history", new HistoryCommand())

Added: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRef.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRef.java?rev=1879938&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRef.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRef.java
 Thu Jul 16 10:04:05 2020
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+package org.apache.jackrabbit.oak.run;
+
+import org.apache.jackrabbit.oak.commons.PathUtils;
+
+import com.google.common.collect.Iterables;
+
+/**
+ * Contains one particular reference to an nt:frozenNode.
+ */
+public class FrozenNodeRef {
+
+    static final String REFERENCE_TO_NT_FROZEN_NODE_FOUND_PREFIX = "Reference 
to nt:frozenNode found : ";
+
+    private final String refPath;
+    private final String refName;
+    private final String refType;
+    private final String refValue;
+    private final String targetPath;
+
+    static boolean isFrozenNodeReferenceCandidate(String uuidRefPath) {
+        boolean isVersionStorage = 
PathUtils.isAncestor("/jcr:system/jcr:versionStorage", uuidRefPath);
+        if (!isVersionStorage) {
+            // TODO: we could consider NOT continuing here, as an 
nt:frozenNode could "in theory"
+            // also show up somewhere else - but it is slightly faster this 
way..
+            return false;
+        }
+        int depth = PathUtils.getDepth(uuidRefPath);
+        if (depth <= 7) {
+            // TODO: we could consider NOT continuing here, as the structure 
of /jcr:system/jcr:versionStorage
+            // could at some point change and this '7' no longer be correct - 
but so far it is and it's faster this way
+            return false;
+        }
+        boolean containsJcrFrozenNodeName = 
Iterables.contains(PathUtils.elements(uuidRefPath), "jcr:frozenNode");
+        if (!containsJcrFrozenNodeName) {
+            // TODO: we could consider NOT continuing here, as we might opt to 
store frozenNodes
+            // under another parent than 'jcr:frozenNode' - but so far that's 
how it is and it's faster
+            return false;
+        }
+        return true;
+    }
+
+    FrozenNodeRef(String refPropertyPath, String refPropertyName, String 
refPropertyType, String refPropertyValue, String targetPath) {
+        this.refPath = refPropertyPath;
+        this.refName = refPropertyName;
+        this.refType = refPropertyType;
+        this.refValue = refPropertyValue;
+        this.targetPath = targetPath;
+    }
+
+    String toInfoString() {
+        return "Node " + refPath + " has a property '" + refName + "'" + " (of 
type " + refType + ")" + " with a value '"
+                + refValue + "'" + " which points to an nt:frozenNode, namely 
to: " + targetPath;
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRef.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsByScanningCommand.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsByScanningCommand.java?rev=1879938&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsByScanningCommand.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsByScanningCommand.java
 Thu Jul 16 10:04:05 2020
@@ -0,0 +1,261 @@
+/*
+ * 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.
+ */
+package org.apache.jackrabbit.oak.run;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.util.Collections;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
+
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.plugins.index.lucene.IndexTracker;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.DocumentQueue;
+import org.apache.jackrabbit.oak.run.cli.CommonOptions;
+import org.apache.jackrabbit.oak.run.cli.NodeStoreFixture;
+import org.apache.jackrabbit.oak.run.cli.NodeStoreFixtureProvider;
+import org.apache.jackrabbit.oak.run.cli.Options;
+import org.apache.jackrabbit.oak.run.commons.Command;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+
+import com.google.common.io.Closer;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
+
+/**
+ * Scans and lists all references to nt:frozenNode and returns an exit code of 
1 if any are found (0 otherwise).
+ * <p/>
+ * This variant does a *very expensive repository scan* for all properties 
formatted as uuid
+ * ( LIKE \"________-____-____-____-____________\" )
+ * and checking if any reference points to an nt:frozenNode (under 
/jcr:system/jcr:versionStorage
+ * at depth > 7).
+ * <p/>
+ * Note that any property with uuid that cannot be resolved will *not be 
reported*, as that
+ * is a legitimate use case of uuid property use. Only uuids that resolve will 
be analysed.
+ * <p/>
+ * Example: 
+ * <pre>
+ * java -mx4g -jar oak-run-*.jar frozennoderefsbyscanning 
mongodb://localhost/&lt;dbname&gt; -user=admin -password=admin
+ * </pre>
+ */
+public class FrozenNodeRefsByScanningCommand implements Command {
+
+    static {
+        // disable any query limits as our query is going to be a full scan, 
and we are aware of it
+        System.setProperty("oak.queryLimitReads", 
String.valueOf(Long.MAX_VALUE));
+
+        //             // disable the WARN of the TraversingCursor
+        //             LoggerContext c = (LoggerContext) 
LoggerFactory.getILoggerFactory();
+        //             Logger logger = 
c.getLogger("org.apache.jackrabbit.oak.plugins.index.Cursors$TraversingCursor");
+        //             logger.setLevel(Level.ERROR);
+    }
+
+    public static final String NAME = "frozennoderefsbyscanning";
+
+    private final String summary = "Scans repository and lists all references 
to nt:frozenNode";
+
+    @Override
+    public void execute(String... args) throws Exception {
+        OptionParser parser = new OptionParser();
+
+        Options opts = new Options();
+        opts.setCommandName(NAME);
+        opts.setSummary(summary);
+        opts.setConnectionString(CommonOptions.DEFAULT_CONNECTION_STRING);
+
+        OptionSpec<String> userOption = parser.accepts("user", "User 
name").withOptionalArg().defaultsTo("admin");
+        OptionSpec<String> passwordOption = parser.accepts("password", 
"Password").withOptionalArg().defaultsTo("admin");
+
+        OptionSet options = opts.parseAndConfigure(parser, args);
+
+        System.out.println("Opening nodestore (readOnly=true)...");
+        // explicitly set readOnly mode (overwriting any possibly set 
-read-write= option)
+        NodeStoreFixture nodeStoreFixture = 
NodeStoreFixtureProvider.create(opts, true);
+        System.out.println("Nodestore opened.");
+
+        int count = uuidscan(userOption, passwordOption, options, 
nodeStoreFixture);
+        if (count > 0) {
+            System.err.println("FAILURE: " + count + " Reference(s) (in any 
uuid formatted property value) to nt:frozenNode found.");
+            System.exit(1);
+        } else {
+            System.out.println("SUCCESS: No references (in any uuid formatted 
property value) to nt:frozenNode found.");
+        }
+    }
+
+    /**
+     * Scans the repository (via an expensive traversing query, ouch, hence it 
needs username/password)
+     * and returns the number of references found. For this, any value 
formatted like a UUID is considered,
+     * then verified if it points to an nt:frozenNode.
+     */
+    private int uuidscan(OptionSpec<String> userOption, OptionSpec<String> 
passwordOption,
+            OptionSet options, NodeStoreFixture nodeStoreFixture) throws 
IOException {
+        NodeStore nodeStore = nodeStoreFixture.getStore();
+        String user = userOption.value(options);
+        String password = passwordOption.value(options);
+
+        Closer closer = Utils.createCloserWithShutdownHook();
+        closer.register(nodeStoreFixture);
+
+        int count = 0;
+
+        try {
+
+            System.out.println("Logging in...");
+            Session session = openSession(nodeStore, "crx.default", user, 
password);
+
+            System.out.println("Logged in, querying...");
+            QueryManager qm = session.getWorkspace().getQueryManager();
+
+            // query for only getting 'Reference' and 'WeakReference' would be 
:
+            // SELECT * FROM [nt:base] AS p WHERE PROPERTY(*, 'Reference') IS 
NOT NULL OR PROPERTY(*, 'WeakReference') IS NOT NULL
+            Query q = qm.createQuery("SELECT * FROM [nt:base] AS p WHERE 
PROPERTY(*, '*') LIKE \"________-____-____-____-____________\"", "JCR-SQL2");
+            QueryResult qr = q.execute();
+            NodeIterator it = qr.getNodes();
+
+            while (it.hasNext()) {
+                Node n = it.nextNode();
+                PropertyIterator pit = n.getProperties();
+                while (pit.hasNext()) {
+                    Property p = pit.nextProperty();
+                    if ("jcr:uuid".equals(p.getName())) {
+                        // jcr:uuid should be skipped as that's the identifier 
of a node, not a reference
+                        continue;
+                    }
+                    if (!p.isMultiple()) {
+                        String propValue = p.getValue().getString();
+                        if 
(propValue.matches("........-....-....-....-............")) {
+                            if (verify(session, n, p, propValue)) {
+                                count++;
+                            }
+                        }
+                    } else {
+                        for (Value v : p.getValues()) {
+                            String propValue = v.getString();
+                            if 
(propValue.matches("........-....-....-....-............")) {
+                                if (verify(session, n, p, propValue)) {
+                                    count++;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            System.out.println("logout...");
+            session.logout();
+            System.out.println("done.");
+
+            return count;
+        } catch (Throwable e) {
+            throw closer.rethrow(e);
+        } finally {
+            closer.close();
+        }
+    }
+
+    private boolean verify(Session session, Node n, Property p, String 
propValue) throws RepositoryException {
+        try {
+            Node node = session.getNodeByIdentifier(propValue);
+            String path = node.getPath();
+            boolean candidate = 
FrozenNodeRef.isFrozenNodeReferenceCandidate(path);
+            if (!candidate) {
+                return false;
+            }
+            Property primaryType = node.getProperty("jcr:primaryType");
+            String primaryTypeValue = primaryType.getString();
+            boolean isNtFrozenNode = "nt:frozenNode".equals(primaryTypeValue);
+            if (!isNtFrozenNode) {
+                // this is where we ultimately have to continue out in any 
case - as only an nt:frozenNode
+                // is what we're interested in.
+                return false;
+            }
+
+            String uuid = propValue;
+            String referrerPath = n.getPath();
+            String referrerProperty = p.getName();
+            FrozenNodeRef ref = new FrozenNodeRef(referrerPath, 
referrerProperty, PropertyType.nameFromValue(p.getType()), uuid, path);
+
+            
System.out.println(FrozenNodeRef.REFERENCE_TO_NT_FROZEN_NODE_FOUND_PREFIX + 
ref.toInfoString());
+
+            return true;
+        } catch (ItemNotFoundException notFound) {
+            // then ignore
+            return false;
+        }
+    }
+
+    // from JsonIndexCommand, slightly modified to be able to set the 
workspace name
+    public static Session openSession(NodeStore nodeStore, String 
workspaceName, String user, String password) throws RepositoryException {
+        if (nodeStore == null) {
+            return null;
+        }
+        StatisticsProvider statisticsProvider = StatisticsProvider.NOOP;
+        Oak oak = new 
Oak(nodeStore).with(ManagementFactory.getPlatformMBeanServer());
+        oak.getWhiteboard().register(StatisticsProvider.class, 
statisticsProvider, Collections.emptyMap());
+        LuceneIndexProvider provider = 
/*JsonIndexCommand.*/createLuceneIndexProvider();
+        oak.with((QueryIndexProvider) provider).with((Observer) 
provider).with(/*JsonIndexCommand.*/createLuceneIndexEditorProvider());
+        Jcr jcr = new Jcr(oak);
+        jcr.with(workspaceName);
+        Repository repository = jcr.createRepository();
+        return repository.login(new SimpleCredentials(user, 
password.toCharArray()));
+    }
+
+    // from JsonIndexCommand, unmodified
+    private static LuceneIndexEditorProvider createLuceneIndexEditorProvider() 
{
+        LuceneIndexEditorProvider ep = new LuceneIndexEditorProvider();
+        ScheduledExecutorService executorService = MoreExecutors
+                
.getExitingScheduledExecutorService((ScheduledThreadPoolExecutor) 
Executors.newScheduledThreadPool(5));
+        StatisticsProvider statsProvider = StatisticsProvider.NOOP;
+        int queueSize = Integer.getInteger("queueSize", 1000);
+        IndexTracker tracker = new IndexTracker();
+        DocumentQueue queue = new DocumentQueue(queueSize, tracker, 
executorService, statsProvider);
+        ep.setIndexingQueue(queue);
+        return ep;
+    }
+
+    // from JsonIndexCommand, unmodified
+    private static LuceneIndexProvider createLuceneIndexProvider() {
+        return new LuceneIndexProvider();
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsByScanningCommand.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsUsingIndexCommand.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsUsingIndexCommand.java?rev=1879938&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsUsingIndexCommand.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsUsingIndexCommand.java
 Thu Jul 16 10:04:05 2020
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+package org.apache.jackrabbit.oak.run;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.run.Utils.NodeStoreOptions;
+import org.apache.jackrabbit.oak.run.commons.Command;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+
+import com.google.common.io.Closer;
+
+/**
+ * Scans and lists all references to nt:frozenNode and returns an exit code of 
1 if any are found (0 otherwise).
+ * <p/>
+ * This variant uses the /oak:index/reference by scanning through that list 
(only)
+ * and checking if any reference points to an nt:frozenNode (under 
/jcr:system/jcr:versionStorage
+ * at depth > 7).
+ * <p/>
+ * Example: 
+ * <pre>
+ * java -mx4g -jar oak-run-*.jar frozennoderefsusingindex 
mongodb://localhost/&lt;dbname&gt;
+ * </pre>
+ */
+public class FrozenNodeRefsUsingIndexCommand implements Command {
+
+    public static final String NAME = "frozennoderefsusingindex";
+
+    @Override
+    public void execute(String... args) throws Exception {
+        String help = NAME + " {<path>|<mongo-uri>|<jdbc-uri>} [options]";
+        Utils.NodeStoreOptions nopts = new 
Utils.NodeStoreOptions(help).parse(args);
+        int count = countNtFrozenNodeReferences(nopts);
+        if (count > 0) {
+            System.err.println("FAILURE: " + count + " Reference(s) (in 
/oak:index/references) to nt:frozenNode found.");
+            System.exit(1);
+        } else {
+            System.out.println("SUCCESS: No references (in 
/oak:index/references) to nt:frozenNode found.");
+        }
+    }
+
+    /**
+     * Browses through /oak:index/references and counts how many references 
therein are
+     * to nt:frozenNode.
+     */
+    private static int countNtFrozenNodeReferences(NodeStoreOptions nopts) 
throws IOException {
+        Closer closer = Utils.createCloserWithShutdownHook();
+        try {
+            // explicitly set readOnly mode
+            System.out.println("Opening NodeStore in read-only mode.");
+            NodeStore store = Utils.bootstrapNodeStore(nopts, closer, true);
+            NodeState root = store.getRoot();
+
+            NodeState oakIndex = root.getChildNode("oak:index");
+            NodeState refIndex = oakIndex.getChildNode("reference");
+            NodeState uuidIndex = oakIndex.getChildNode("uuid");
+            NodeState uuids = uuidIndex.getChildNode(":index");
+            System.out.println("Scanning ... refindex = " + refIndex);
+            NodeState references = refIndex.getChildNode(":references");
+
+            int count = 0;
+
+            for (ChildNodeEntry child : references.getChildNodeEntries()) {
+                String uuid = child.getName();
+                NodeState uuidRef = uuids.getChildNode(uuid);
+
+                PropertyState entry = uuidRef.getProperty("entry");
+                Iterable<String> uuidRefPaths = entry.getValue(Type.STRINGS);
+                for (String uuidRefPath : uuidRefPaths) {
+                    if 
(!FrozenNodeRef.isFrozenNodeReferenceCandidate(uuidRefPath)) {
+                        continue;
+                    }
+                    NodeState p = getNodeByPath(root, uuidRefPath);
+                    PropertyState primaryType = 
p.getProperty("jcr:primaryType");
+                    boolean isNtFrozenNode = 
"nt:frozenNode".equals(primaryType.getValue(Type.NAME));
+                    if (!isNtFrozenNode) {
+                        // this is where we ultimately have to continue out in 
any case - as only an nt:frozenNode
+                        // is what we're interested in.
+                        continue;
+                    }
+                    count += countFrozenNodeReferences(root, child, 
uuidRefPath, p);
+                }
+            }
+            System.out.println("Scanning done.");
+
+            return count;
+        } catch (Throwable e) {
+            throw closer.rethrow(e);
+        } finally {
+            closer.close();
+        }
+    }
+
+    private static int countFrozenNodeReferences(NodeState root, 
ChildNodeEntry referenceChild, String uuidRefPath,
+            NodeState resolvedNodeState) {
+        String uuid = referenceChild.getName();
+        List<String> paths = new LinkedList<String>();
+        scanReferrerPaths(paths, "/", referenceChild.getNodeState());
+        int count = 0;
+        for (String referrerPropertyPath : paths) {
+            String referrerPath = 
PathUtils.getAncestorPath(referrerPropertyPath, 1);
+            String referrerProperty = PathUtils.getName(referrerPropertyPath);
+            FrozenNodeRef ref = new FrozenNodeRef(referrerPath, 
referrerProperty, "Reference", uuid, uuidRefPath);
+            
System.out.println(FrozenNodeRef.REFERENCE_TO_NT_FROZEN_NODE_FOUND_PREFIX + 
ref.toInfoString());
+            count++;
+        }
+        return count;
+    }
+
+    private static void scanReferrerPaths(List<String> paths, String 
parentPath, NodeState referenceChildNodeState) {
+        for (ChildNodeEntry e : referenceChildNodeState.getChildNodeEntries()) 
{
+            String childName = e.getName();
+            NodeState childNode = e.getNodeState();
+            String childPath = PathUtils.concat(parentPath, childName);
+            PropertyState p = childNode.getProperty("match");
+            if (p != null && p.getValue(Type.BOOLEAN)) {
+                paths.add(childPath);
+            }
+            scanReferrerPaths(paths, childPath, childNode);
+        }
+    }
+
+    private static NodeState getNodeByPath(NodeState base, String path) {
+        Iterable<String> elems = PathUtils.elements(path);
+        NodeState n = base;
+        for (String elem : elems) {
+            n = n.getChildNode(elem);
+        }
+        return n;
+    }
+
+}

Propchange: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/FrozenNodeRefsUsingIndexCommand.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Utils.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Utils.java?rev=1879938&r1=1879937&r2=1879938&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Utils.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Utils.java
 Thu Jul 16 10:04:05 2020
@@ -57,6 +57,7 @@ import org.apache.jackrabbit.oak.run.cli
 import org.apache.jackrabbit.oak.segment.SegmentNodeStoreBuilders;
 import org.apache.jackrabbit.oak.segment.file.FileStore;
 import org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException;
+import org.apache.jackrabbit.oak.segment.file.ReadOnlyFileStore;
 import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore;
 import org.apache.jackrabbit.oak.spi.state.NodeStore;
 import org.jetbrains.annotations.Nullable;
@@ -167,6 +168,10 @@ class Utils {
     }
 
     public static NodeStore bootstrapNodeStore(NodeStoreOptions options, 
Closer closer) throws IOException, InvalidFileStoreVersionException {
+        return bootstrapNodeStore(options, closer, false);
+    }
+
+    public static NodeStore bootstrapNodeStore(NodeStoreOptions options, 
Closer closer, boolean readOnlyMode) throws IOException, 
InvalidFileStoreVersionException {
         String src = options.getStoreArg();
         if (src == null || src.length() == 0) {
             options.printHelpOn(System.err);
@@ -176,12 +181,23 @@ class Utils {
         if (src.startsWith(MongoURI.MONGODB_PREFIX) || src.startsWith("jdbc")) 
{
             DocumentNodeStoreBuilder<?> builder = 
createDocumentMKBuilder(options, closer);
             if (builder != null) {
+                if (readOnlyMode) {
+                    builder.setReadOnlyMode();
+                } // otherwise default is read-write
                 DocumentNodeStore store = builder.build();
                 closer.register(asCloseable(store));
                 return store;
             }
         }
 
+        if (readOnlyMode) {
+            ReadOnlyFileStore fileStore = fileStoreBuilder(new File(src))
+                .withStrictVersionCheck(true)
+                .buildReadOnly();
+            closer.register(fileStore);
+            return SegmentNodeStoreBuilders.builder(fileStore).build();
+        }
+
         FileStore fileStore = fileStoreBuilder(new File(src))
             .withStrictVersionCheck(true)
             .build();

Added: 
jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/FrozenNodeReferenceCreator.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/FrozenNodeReferenceCreator.java?rev=1879938&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/FrozenNodeReferenceCreator.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/FrozenNodeReferenceCreator.java
 Thu Jul 16 10:04:05 2020
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+package org.apache.jackrabbit.oak.run;
+
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.oak.run.cli.CommonOptions;
+import org.apache.jackrabbit.oak.run.cli.NodeStoreFixture;
+import org.apache.jackrabbit.oak.run.cli.NodeStoreFixtureProvider;
+import org.apache.jackrabbit.oak.run.cli.Options;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+
+import com.google.common.io.Closer;
+
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
+
+/**
+ * Test class that simplifies creation of a property of type Reference, as 
that's not
+ * easily achievable without a tool.
+ * <p/>
+ * The idea is that this might help testing the corresponding 
FrozenNodeRefsByScanningCommand
+ * and FrozenNodeRefsUsingIndexCommand commands.
+ * <p/>
+ * Example:
+ * <pre>
+ * java -mx4g -cp oak-run-*-tests.jar 
org.apache.jackrabbit.oak.run.FrozenNodeReferenceCreator 
mongodb://localhost/&lt;dbname&gt; -user=admin -password=admin 
-testCreateRefPath=&lt;mypath&gt; -testCreateRefProp=&lt;mypropertyname&gt; 
-testCreateRefUuid=&lt;myuuid&gt; -read-write=true
+ * </pre>
+ */
+public class FrozenNodeReferenceCreator {
+
+    public static void main(String[] args) throws Exception {
+        OptionParser parser = new OptionParser();
+
+        Options opts = new Options();
+        opts.setConnectionString(CommonOptions.DEFAULT_CONNECTION_STRING);
+
+        OptionSpec<String> userOption = parser.accepts("user", "User 
name").withOptionalArg().defaultsTo("admin");
+        OptionSpec<String> passwordOption = parser.accepts("password", 
"Password").withOptionalArg().defaultsTo("admin");
+
+        OptionSpec<String> testCreateRefPathOption = 
parser.accepts("testCreateRefPath", "FOR TESTING ONLY: path where to create a 
reference from")
+                .withRequiredArg();
+        OptionSpec<String> testCreateRefPropOption = 
parser.accepts("testCreateRefProp", "FOR TESTING ONLY: property name for create 
a reference")
+                .withRequiredArg();
+        OptionSpec<String> testCreateRefTypeOption = parser
+                .accepts("testCreateRefType", "FOR TESTING ONLY: property 
type: 'reference' or anything else for a plain String").withOptionalArg();
+        OptionSpec<String> testCreateRefUuidOption = 
parser.accepts("testCreateRefUuid", "FOR TESTING ONLY: uuid to use as the 
reference")
+                .withRequiredArg();
+
+        OptionSet options = opts.parseAndConfigure(parser, args);
+
+        System.out.println("Opening nodestore...");
+        NodeStoreFixture nodeStoreFixture = 
NodeStoreFixtureProvider.create(opts);
+        System.out.println("Nodestore opened.");
+
+        NodeStore nodeStore = nodeStoreFixture.getStore();
+        String user = userOption.value(options);
+        String password = passwordOption.value(options);
+
+        String createRefPath = testCreateRefPathOption.value(options);
+        String createRefProp = testCreateRefPropOption.value(options);
+        String createRefType = testCreateRefTypeOption.value(options);
+        String createRefUuid = testCreateRefUuidOption.value(options);
+
+        Closer closer = Utils.createCloserWithShutdownHook();
+        closer.register(nodeStoreFixture);
+        try {
+
+            System.out.println("Logging in...");
+            Session session = 
FrozenNodeRefsByScanningCommand.openSession(nodeStore, "crx.default", user, 
password);
+
+            System.out.println(
+                    "Logged in, creating test reference: " + "path=" + 
createRefPath + ", property=" + createRefProp + ", uuid=" + createRefUuid);
+            Node n = session.getNode(createRefPath);
+            Value v;
+            if ("reference".equals(createRefType)) {
+                v = session.getValueFactory().createValue(createRefUuid, 
PropertyType.REFERENCE);
+                System.out.println("Creating property of type REFERENCE");
+            } else {
+                v = session.getValueFactory().createValue(createRefUuid, 
PropertyType.STRING);
+                System.out.println("Creating property of type STRING");
+            }
+            n.setProperty(createRefProp, v);
+            session.save();
+            System.out.println("Created. Done.");
+            session.logout();
+            closer.close();
+
+        } catch (Throwable e) {
+            throw closer.rethrow(e);
+        } finally {
+            closer.close();
+        }
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/FrozenNodeReferenceCreator.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to