Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentImportTest.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentImportTest.java (revision 1179582) +++ 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 1179582) +++ 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/persistence/pool/BundleDbPersistenceManager.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/BundleDbPersistenceManager.java (revision 1179582) +++ 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; @@ -63,6 +66,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.sun.corba.se.impl.protocol.giopmsgheaders.Message; + /** * This is a generic persistence manager that stores the {@link NodePropBundle}s * in a database. @@ -85,7 +90,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 +716,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 +738,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 +775,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 +792,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 +811,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 +878,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 +911,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 +960,7 @@ continue; } - checkBundleConsistency(id, bundle, fix, modifications); + checkBundleConsistency(id, bundle, fix, modifications, reports); if (recursive) { for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) { @@ -923,6 +996,8 @@ } log.info(name + ": checked " + count + "/" + total + " bundles."); + + return total; } /** @@ -1453,5 +1528,4 @@ // owning BundleDbPersistenceManager } } - }