Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentImportTest.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentImportTest.java (revision 1179654) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentImportTest.java (working copy) @@ -23,11 +23,14 @@ import javax.jcr.ImportUUIDBehavior; import javax.jcr.InvalidItemStateException; import javax.jcr.Node; +import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.core.persistence.ConsistencyChecker; import org.apache.jackrabbit.spi.Name; +import org.apache.jackrabbit.test.NotExecutableException; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; @@ -51,9 +54,14 @@ * performed by the threads. */ private static final int NUM_NODES = 10; - + public void testConcurrentImport() throws RepositoryException { - concurrentImport(new String[]{JcrConstants.MIX_REFERENCEABLE}, false); + try { + concurrentImport(new String[]{JcrConstants.MIX_REFERENCEABLE}, false); + } + finally { + checkConsistency(); + } } public void testConcurrentImportSynced() throws RepositoryException { @@ -206,4 +214,18 @@ } -} \ No newline at end of file + private void checkConsistency() throws RepositoryException { + Repository r = testRootNode.getSession().getRepository(); + if (r instanceof RepositoryImpl) { + RepositoryImpl ri = (RepositoryImpl)r; + try { + ConsistencyChecker.Report rep = TestHelper.checkConsistency(testRootNode.getSession().getWorkspace().getName(), ri); + System.err.println(rep.getNodes() + " nodes checked in " + rep.getElapsedTime() + "ms"); + assertEquals(0, rep.getItems().size()); + } + catch (NotExecutableException ex) { + // ignore + } + } + } +} Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestHelper.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestHelper.java (revision 1179654) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestHelper.java (working copy) @@ -18,6 +18,10 @@ import javax.jcr.RepositoryException; +import org.apache.jackrabbit.core.persistence.ConsistencyChecker; +import org.apache.jackrabbit.core.persistence.PersistenceManager; +import org.apache.jackrabbit.test.NotExecutableException; + /** * TestHelper provides test utility methods. */ @@ -35,4 +39,24 @@ throws RepositoryException { repo.getWorkspaceInfo(name).dispose(); } + + /** + * Runs a consistency check on the given workspace. + * + * @param name the name of the workspace to perform the consistency check. + * @param repo the repository instance. + * @throws RepositoryException if an error occurs while getting the + * workspace with the given name. + */ + public static ConsistencyChecker.Report checkConsistency(String name, + RepositoryImpl repo) throws NotExecutableException, + RepositoryException { + RepositoryImpl.WorkspaceInfo wspInfo = repo.getWorkspaceInfo(name); + PersistenceManager pm = wspInfo.getPersistenceManager(); + if (!(pm instanceof ConsistencyChecker)) { + throw new NotExecutableException(); + } else { + return ((ConsistencyChecker) pm).check(null, true, false); + } + } } Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java (revision 1179654) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java (working copy) @@ -2048,7 +2048,7 @@ */ protected void doVersionRecovery() throws RepositoryException { // JCR-2551: Recovery from a lost version history - if (Boolean.getBoolean("org.apache.jackrabbit.version.recovery")) { + if (! Boolean.getBoolean("org.apache.jackrabbit.version.recovery")) { RepositoryChecker checker = new RepositoryChecker( persistMgr, context.getInternalVersionManager()); checker.check(ROOT_NODE_ID, true); Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/ConsistencyChecker.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/ConsistencyChecker.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/ConsistencyChecker.java (revision 0) @@ -0,0 +1,65 @@ +/* + * 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.core.persistence; + +import java.util.Set; + +import javax.jcr.RepositoryException; + +/** + * Optional interface for Persistence Managers. Allows running consistency + * checks similar to the base one (see @link + * {@link PersistenceManager#checkConsistency(String[], boolean, boolean)) but + * providing a result that can be acted upon. + */ +public interface ConsistencyChecker { + + public interface ReportItem { + + public String getNodeId(); + + public String getMessage(); + } + + public interface Report { + + public int getNodes(); + + public long getElapsedTime(); + + public Set getItems(); + } + + /** + * Perform a consistency check of the data. An example are non-existent + * nodes referenced in a child node entry. The existence of this feature and + * the scope of the implementation can vary in different PersistenceManager + * implementations. + * + * @param uuids + * list of UUIDs of nodes to be checked. if null, all nodes will + * be checked + * @param recursive + * if true, the tree(s) below the given node(s) will be traversed + * and checked as well + * @param fix + * if true, any problems found that can be repaired will be + * repaired. if false, no data will be modified, instead all + * inconsistencies will only get logged + */ + Report check(String[] uuids, boolean recursive, boolean fix) throws RepositoryException; +} Property changes on: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/ConsistencyChecker.java ___________________________________________________________________ Added: svn:executable + * Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/BundleDbPersistenceManager.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/BundleDbPersistenceManager.java (revision 1179654) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/BundleDbPersistenceManager.java (working copy) @@ -27,9 +27,11 @@ import java.sql.Types; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Random; +import java.util.Set; import javax.jcr.RepositoryException; import javax.sql.DataSource; @@ -40,6 +42,7 @@ import org.apache.jackrabbit.core.fs.local.LocalFileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; +import org.apache.jackrabbit.core.persistence.ConsistencyChecker; import org.apache.jackrabbit.core.persistence.PMContext; import org.apache.jackrabbit.core.persistence.bundle.AbstractBundlePersistenceManager; import org.apache.jackrabbit.core.persistence.util.BLOBStore; @@ -85,7 +88,7 @@ * */ public class BundleDbPersistenceManager - extends AbstractBundlePersistenceManager implements DatabaseAware { + extends AbstractBundlePersistenceManager implements DatabaseAware, ConsistencyChecker { /** the default logger */ private static Logger log = LoggerFactory.getLogger(BundleDbPersistenceManager.class); @@ -711,7 +714,8 @@ * {@linkplain NodePropBundle bundles} here */ protected void checkBundleConsistency(NodeId id, NodePropBundle bundle, - boolean fix, Collection modifications) { + boolean fix, Collection modifications, + Set reports) { //log.info(name + ": checking bundle '" + id + "'"); // skip all system nodes except root node @@ -732,20 +736,25 @@ try { // analyze child node bundles NodePropBundle child = loadBundle(entry.getId()); + String message = null; if (child == null) { - log.error( - "NodeState '" + id + "' references inexistent child" - + " '" + entry.getName() + "' with id " - + "'" + entry.getId() + "'"); + message = "NodeState '" + id + "' references inexistent child" + " '" + + entry.getName() + "' with id " + "'" + entry.getId() + "'"; + log.error(message); missingChildren.add(entry); } else { NodeId cp = child.getParentId(); if (cp == null) { - log.error("ChildNode has invalid parent uuid: "); + message = "ChildNode has invalid parent uuid: "; + log.error(message); } else if (!cp.equals(id)) { - log.error("ChildNode has invalid parent uuid: '" + cp + "' (instead of '" + id + "')"); + message = "ChildNode has invalid parent uuid: '" + cp + "' (instead of '" + id + "')"; + log.error(message); } } + if (message != null && reports != null) { + reports.add(new ReportItemImpl(id, message)); + } } catch (ItemStateException e) { // problem already logged (loadBundle called with logDetailedErrors=true) } @@ -764,7 +773,11 @@ // skip root nodes (that point to itself) if (parentId != null && !id.toString().endsWith("babecafebabe")) { if (loadBundle(parentId) == null) { - log.error("NodeState '" + id + "' references inexistent parent uuid '" + parentId + "'"); + String message = "NodeState '" + id + "' references inexistent parent uuid '" + parentId + "'"; + log.error(message); + if (reports != null) { + reports.add(new ReportItemImpl(id, message)); + } } NodePropBundle parentBundle = loadBundle(parentId); Iterator childNodeIter = parentBundle.getChildNodeEntries().iterator(); @@ -777,7 +790,12 @@ } } if (!found) { - log.error("NodeState '" + id + "' is not referenced by its parent node '" + parentId + "'"); + String message = "NodeState '" + id + "' is not referenced by its parent node '" + parentId + "'"; + log.error(message); + if (reports != null) { + reports.add(new ReportItemImpl(id, message)); + } + int l = (int) System.currentTimeMillis(); int r = new Random().nextInt(); int n = l + r; @@ -791,14 +809,66 @@ log.error("Error reading node '" + parentId + "' (parent of '" + id + "'): " + e); } } + + private static class ReportItemImpl implements ConsistencyChecker.ReportItem { + private final String nodeId, message; + + public ReportItemImpl(NodeId id, String message) { + this.nodeId = id.toString(); + this.message = message; + } + + public String getNodeId() { + return nodeId; + } + + public String getMessage() { + return message; + } + + } + + public Report check(String[] uuids, boolean recursive, boolean fix) throws RepositoryException { + + final Set reports = new HashSet(); + + long tstart = System.currentTimeMillis(); + final int total = internalCheckConsistency(uuids, recursive, fix, reports); + final long elapsed = System.currentTimeMillis() - tstart; + + return new Report() { + + public int getNodes() { + return total; + } + + public long getElapsedTime() { + return elapsed; + } + + public Set getItems() { + return reports; + } + }; + } + public void checkConsistency(String[] uuids, boolean recursive, boolean fix) { + try { + internalCheckConsistency(uuids, recursive, fix, null); + } + catch (RepositoryException ex) { + log.error("While running consistency check.", ex); + } + } + + private int internalCheckConsistency(String[] uuids, boolean recursive, boolean fix, Set reports) throws RepositoryException { int count = 0; int total = 0; Collection modifications = new ArrayList(); if (uuids == null) { - // get all node bundles in the database with a single sql statement, + // get all node bundles in the database with a single SQL statement, // which is (probably) faster than loading each bundle and traversing the tree ResultSet rs = null; try { @@ -806,8 +876,9 @@ rs = conHelper.exec(sql, new Object[0], false, 0); try { if (!rs.next()) { - log.error("Could not retrieve total number of bundles. empty result set."); - return; + String message = "Could not retrieve total number of bundles. empty result set."; + log.error(message); + throw new RepositoryException(message); } total = rs.getInt(1); } finally { @@ -838,7 +909,7 @@ } // parse and check bundle NodePropBundle bundle = readBundle(id, bRs, 1); - checkBundleConsistency(id, bundle, fix, modifications); + checkBundleConsistency(id, bundle, fix, modifications, reports); } catch (SQLException e) { log.error("Unable to parse bundle " + id, e); } finally { @@ -887,7 +958,7 @@ continue; } - checkBundleConsistency(id, bundle, fix, modifications); + checkBundleConsistency(id, bundle, fix, modifications, reports); if (recursive) { for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) { @@ -923,6 +994,8 @@ } log.info(name + ": checked " + count + "/" + total + " bundles."); + + return total; } /** @@ -1453,5 +1526,4 @@ // owning BundleDbPersistenceManager } } - }