Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/VersionManagerImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/VersionManagerImpl.java (revision 997303) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/VersionManagerImpl.java (revision ) @@ -413,7 +413,7 @@ Permission.VERSION_MNGMT); List failedIds = new LinkedList(); mergeOrUpdate(state, srcWorkspaceName, failedIds, bestEffort, isShallow); - return new LazyItemIterator(session.getItemManager(), failedIds); + return new LazyItemIterator(context, failedIds); } public String toString() { return "versionManager.merge(" @@ -592,7 +592,7 @@ } List failedIds = new ArrayList(); merge(activity, failedIds); - return new LazyItemIterator(session.getItemManager(), failedIds); + return new LazyItemIterator(context, failedIds); } /** @@ -642,4 +642,4 @@ } } -} \ No newline at end of file +} Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java (revision 1027183) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java (revision ) @@ -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 + try { - NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); - childNode.onRemove(getNodeId()); + NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); + childNode.onRemove(getNodeId()); + } catch (ItemNotFoundException e) { + boolean ignoreError = false; + if (sessionContext.getSessionImpl().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); + try { - NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); - childNode.onRemove(thisState.getNodeId()); - // remove the child node entry + NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); + childNode.onRemove(thisState.getNodeId()); + // remove the child node entry + } catch (ItemNotFoundException e) { + boolean ignoreError = false; + if (parentId != null && sessionContext.getSessionImpl().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("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: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java (revision 1022410) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java (revision ) @@ -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: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java (revision 1005019) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java (revision ) @@ -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 removing 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,20 @@ } /** + * Checks whether repository inconsistencies should be automatically fixed + * when traversing over child nodes, when trying to add a child node, or + * when removing a child node. + * + * @return true if the {@link #AUTO_FIX_CORRUPTIONS} + * attribute is 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: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/AutoFixCorruptNode.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/AutoFixCorruptNode.java (revision ) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/AutoFixCorruptNode.java (revision ) @@ -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("org.apache.jackrabbit.autoFixCorruptions", "true"); + } + return rep.login(cred); + } + +} Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/TestAll.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/TestAll.java (revision ) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/TestAll.java (revision ) @@ -0,0 +1,39 @@ +/* + * 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 junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Test suite that includes all test cases for this package. + */ +public class TestAll extends TestCase { + + /** + * Returns a Test suite that executes all tests inside this + * package. + */ + public static Test suite() { + TestSuite suite = new TestSuite("org.apache.jackrabbit.core.persistence tests"); + + suite.addTestSuite(AutoFixCorruptNode.class); + + return suite; + } +} Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java (revision 792437) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java (revision ) @@ -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 context used to access the repository. + */ + private final SessionContext sessionContext; + /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; @@ -71,11 +78,11 @@ /** * Creates a new LazyItemIterator instance. * - * @param itemMgr item manager + * @param sessionContext session context * @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 sessionContext session context * @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);