();
merge(activity, failedIds);
- return new LazyItemIterator(session.getItemManager(), failedIds);
+ return new LazyItemIterator(context, failedIds);
}
/**
Index: src/main/java/org/apache/jackrabbit/core/ItemManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/ItemManager.java (revision 1039060)
+++ src/main/java/org/apache/jackrabbit/core/ItemManager.java (working copy)
@@ -713,7 +713,7 @@
childIds.add(entry.getId());
}
- return new LazyItemIterator(this, childIds, parentId);
+ return new LazyItemIterator(sessionContext, childIds, parentId);
}
/**
@@ -774,7 +774,7 @@
childIds.add(id);
}
- return new LazyItemIterator(this, childIds);
+ return new LazyItemIterator(sessionContext, childIds);
}
//-------------------------------------------------< item factory methods >
Index: src/test/java/org/apache/jackrabbit/core/persistence/AutoFixCorruptNode.java
===================================================================
--- src/test/java/org/apache/jackrabbit/core/persistence/AutoFixCorruptNode.java (revision 0)
+++ src/test/java/org/apache/jackrabbit/core/persistence/AutoFixCorruptNode.java (revision 0)
@@ -0,0 +1,119 @@
+/*
+ * 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.io.File;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.util.UUID;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import junit.framework.TestCase;
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.TransientRepository;
+
+/**
+ * Tests that a corrupt node is automatically fixed.
+ */
+public class AutoFixCorruptNode extends TestCase {
+
+ private final String TEST_DIR = "target/temp/" + getClass().getSimpleName();
+
+ public void setUp() throws Exception {
+ FileUtils.deleteDirectory(new File(TEST_DIR));
+ }
+
+ public void tearDown() throws Exception {
+ setUp();
+ }
+
+ public void testAutoFix() throws Exception {
+
+ // new repository
+ TransientRepository rep = new TransientRepository(new File(TEST_DIR));
+ Session s = openSession(rep, false);
+ Node root = s.getRootNode();
+
+ // add nodes /test and /test/missing
+ Node test = root.addNode("test");
+ Node missing = test.addNode("missing");
+ missing.addMixin("mix:referenceable");
+ UUID id = UUID.fromString(missing.getIdentifier());
+ s.save();
+ s.logout();
+
+ // remove the bundle for /test/missing directly in the database
+ Connection conn = DriverManager.getConnection(
+ "jdbc:derby:"+TEST_DIR+"/workspaces/default/db");
+ PreparedStatement prep = conn.prepareStatement(
+ "delete from DEFAULT_BUNDLE where NODE_ID_HI=? and NODE_ID_LO=?");
+ prep.setLong(1, id.getMostSignificantBits());
+ prep.setLong(2, id.getLeastSignificantBits());
+ prep.executeUpdate();
+ conn.close();
+
+ // login and try the operation
+ s = openSession(rep, false);
+ test = s.getRootNode().getNode("test");
+
+ // try to add a node with the same name
+ try {
+ test.addNode("missing");
+ s.save();
+ } catch (RepositoryException e) {
+ // expected
+ }
+
+ s.logout();
+
+ s = openSession(rep, true);
+ test = s.getRootNode().getNode("test");
+ // iterate over all child nodes fixes the corruption
+ NodeIterator it = test.getNodes();
+ while (it.hasNext()) {
+ it.nextNode();
+ }
+
+ // try to add a node with the same name
+ test.addNode("missing");
+ s.save();
+
+ // try to delete the parent node
+ test.remove();
+ s.save();
+
+ s.logout();
+ rep.shutdown();
+
+ FileUtils.deleteDirectory(new File("repository"));
+
+ }
+
+ private Session openSession(Repository rep, boolean autoFix) throws RepositoryException {
+ SimpleCredentials cred = new SimpleCredentials("admin", "admin".toCharArray());
+ if (autoFix) {
+ cred.setAttribute("jackrabbit.autoFix", "true");
+ }
+ return rep.login(cred);
+ }
+
+}
Index: src/main/java/org/apache/jackrabbit/core/SessionImpl.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/SessionImpl.java (revision 1039060)
+++ src/main/java/org/apache/jackrabbit/core/SessionImpl.java (working copy)
@@ -122,6 +122,17 @@
public static final String DISABLE_CLUSTER_SYNC_ON_REFRESH =
"org.apache.jackrabbit.disableClusterSyncOnRefresh";
+ /**
+ * Name of the session attribute that controls whether repository
+ * inconsistencies should be automatically fixed when traversing over child
+ * nodes, when trying to add a child node, or remove a child node.
+ *
+ * @since Apache Jackrabbit 2.2
+ * @see JCR-2740
+ */
+ public static final String AUTO_FIX_CORRUPTIONS =
+ "org.apache.jackrabbit.autoFixCorruptions";
+
private static Logger log = LoggerFactory.getLogger(SessionImpl.class);
/**
@@ -772,6 +783,22 @@
}
/**
+ * Automatically fixes repository inconsistencies when traversing over child nodes,
+ * when trying to add a child node, or remove a child node.
+ *
+ * Subclasses can override this method to implement alternative
+ * rules on when cluster synchronization should be done.
+ *
+ * @return true if the {@link #AUTO_FIX_CORRUPTIONS}
+ * attribute is not set, false otherwise
+ * @since Apache Jackrabbit 2.2
+ * @see JCR-2740
+ */
+ protected boolean autoFixCorruptions() {
+ return getAttribute(AUTO_FIX_CORRUPTIONS) == null;
+ }
+
+ /**
* {@inheritDoc}
*/
public boolean hasPendingChanges() throws RepositoryException {
Index: src/main/java/org/apache/jackrabbit/core/NodeImpl.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/NodeImpl.java (revision 1039060)
+++ src/main/java/org/apache/jackrabbit/core/NodeImpl.java (working copy)
@@ -98,6 +98,7 @@
import org.apache.jackrabbit.core.state.ChildNodeEntry;
import org.apache.jackrabbit.core.state.ItemState;
import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.ItemStateManager;
import org.apache.jackrabbit.core.state.NodeReferences;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.core.state.PropertyState;
@@ -555,7 +556,7 @@
* @param id
* @param newName
* @throws RepositoryException
- * @deprecated use #renameChildNode(NodeId, Name, boolean)
+ * @deprecated use #renameChildNode(NodeId, Name, boolean)
*/
protected void renameChildNode(Name oldName, int index, NodeId id,
Name newName)
@@ -613,8 +614,24 @@
}
// notify target of removal
- NodeImpl childNode = itemMgr.getNode(childId, getNodeId());
- childNode.onRemove(getNodeId());
+ try {
+ NodeImpl childNode = itemMgr.getNode(childId, getNodeId());
+ childNode.onRemove(getNodeId());
+ } catch (ItemNotFoundException e) {
+ boolean ignoreError = false;
+ if (itemMgr.session.autoFixCorruptions()) {
+ // it might be an access right problem
+ // we need to check if the item doesn't exist in the ism
+ ItemStateManager ism = sessionContext.getItemStateManager();
+ if (!ism.hasItemState(childId)) {
+ log.warn("Node " + childId + " not found, ignore", e);
+ ignoreError = true;
+ }
+ }
+ if (!ignoreError) {
+ throw e;
+ }
+ }
// remove the child node entry
if (!thisState.removeChildNodeEntry(childId)) {
@@ -663,9 +680,28 @@
// recursively remove child node
NodeId childId = entry.getId();
//NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId);
- NodeImpl childNode = itemMgr.getNode(childId, getNodeId());
- childNode.onRemove(thisState.getNodeId());
- // remove the child node entry
+ try {
+ NodeImpl childNode = itemMgr.getNode(childId, getNodeId());
+ childNode.onRemove(thisState.getNodeId());
+ // remove the child node entry
+ } catch (ItemNotFoundException e) {
+ boolean ignoreError = false;
+ if (parentId != null && itemMgr.session.autoFixCorruptions()) {
+ // it might be an access right problem
+ // we need to check if the item doesn't exist in the ism
+ ItemStateManager ism = itemMgr.sessionContext.getItemStateManager();
+ if (!ism.hasItemState(childId)) {
+ log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " +
+ "node id " + childId + ") " +
+ "not found when trying to remove " + getPath() + " " +
+ "(node id " + getNodeId() + ") - ignored", e);
+ ignoreError = true;
+ }
+ }
+ if (!ignoreError) {
+ throw e;
+ }
+ }
thisState.removeChildNodeEntry(childId);
}
}
@@ -1418,7 +1454,7 @@
log.debug(msg);
throw new AccessDeniedException(msg);
}
-
+
ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries());
int srcInd = -1, destInd = -1;
for (int i = 0; i < list.size(); i++) {
@@ -3022,7 +3058,7 @@
}
idList = filteredList;
}
- return new LazyItemIterator(itemMgr, idList);
+ return new LazyItemIterator(sessionContext, idList);
} else {
// there are no references, return empty iterator
return PropertyIteratorAdapter.EMPTY;
Index: src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java (revision 1039060)
+++ src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java (working copy)
@@ -31,6 +31,8 @@
import org.apache.jackrabbit.core.id.ItemId;
import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.session.SessionContext;
+import org.apache.jackrabbit.core.state.ItemStateManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,6 +55,11 @@
/** Logger instance for this class */
private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class);
+ /**
+ * The session used to access the repository.
+ */
+ private final SessionContext sessionContext;
+
/** the item manager that is used to lazily fetch the items */
private final ItemManager itemMgr;
@@ -74,8 +81,8 @@
* @param itemMgr item manager
* @param idList list of item id's
*/
- public LazyItemIterator(ItemManager itemMgr, List< ? extends ItemId> idList) {
- this(itemMgr, idList, null);
+ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) {
+ this(sessionContext, idList, null);
}
/**
@@ -83,12 +90,13 @@
* a parent id as parameter. This version should be invoked to strictly return
* children nodes of a node.
*
- * @param itemMgr item manager
+ * @param session the session
* @param idList list of item id's
* @param parentId parent id.
*/
- public LazyItemIterator(ItemManager itemMgr, List< ? extends ItemId> idList, NodeId parentId) {
- this.itemMgr = itemMgr;
+ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) {
+ this.sessionContext = sessionContext;
+ this.itemMgr = sessionContext.getSessionImpl().getItemManager();
this.idList = new ArrayList(idList);
this.parentId = parentId;
// prefetch first item
@@ -117,6 +125,24 @@
log.debug("ignoring nonexistent item " + id);
// remove invalid id
idList.remove(pos);
+
+ // maybe fix the root cause
+ if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) {
+ try {
+ // it might be an access right problem
+ // we need to check if the item doesn't exist in the ism
+ ItemStateManager ism = sessionContext.getItemStateManager();
+ if (!ism.hasItemState(id)) {
+ NodeImpl p = (NodeImpl) itemMgr.getItem(parentId);
+ p.removeChildNode((NodeId) id);
+ p.save();
+ }
+ } catch (RepositoryException e2) {
+ log.error("could not fix repository inconsistency", e);
+ // ignore
+ }
+ }
+
// try next
} catch (AccessDeniedException e) {
log.debug("ignoring nonexistent item " + id);