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
}
}
-
}