You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2008/03/03 10:30:58 UTC
svn commit: r633001 -
/jackrabbit/branches/1.3/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/ConsistencyCheckTest.java
Author: jukka
Date: Mon Mar 3 01:30:56 2008
New Revision: 633001
URL: http://svn.apache.org/viewvc?rev=633001&view=rev
Log:
JCR-1428: Add API for selective bundle consistency check (Jackrabbit-specific)
- 1.3: Patch by Alexander Klimetschek (forgot to include test case)
Added:
jackrabbit/branches/1.3/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/ConsistencyCheckTest.java
Added: jackrabbit/branches/1.3/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/ConsistencyCheckTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.3/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/ConsistencyCheckTest.java?rev=633001&view=auto
==============================================================================
--- jackrabbit/branches/1.3/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/ConsistencyCheckTest.java (added)
+++ jackrabbit/branches/1.3/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/ConsistencyCheckTest.java Mon Mar 3 01:30:56 2008
@@ -0,0 +1,444 @@
+/*
+ * 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.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Properties;
+
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.version.Version;
+
+import junit.framework.TestCase;
+
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.test.JUnitTest;
+import org.apache.jackrabbit.test.config.PersistenceManagerConf;
+import org.apache.jackrabbit.test.config.RepositoryConf;
+import org.apache.jackrabbit.uuid.UUID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>ConsistencyCheckTest</code> tests the PersistenceManager's consistencyCheck feature.
+ *
+ * For analysis, run this test with log4j enabled (see below).
+ *
+ */
+public class ConsistencyCheckTest extends TestCase {
+
+ private static Logger log = LoggerFactory.getLogger(JUnitTest.class);
+
+ //-------------------------------------------------------------------< generic utility methods >
+
+ public static File createTempDir(String prefix, String suffix) {
+ try {
+ File dir = File.createTempFile(prefix, suffix);
+ dir.delete();
+ dir.mkdir();
+
+ return dir;
+ } catch (IOException e) {
+ fail("Cannot create temp directory for test: " + e);
+ return null;
+ }
+ }
+
+ public static void delete(File file) {
+ File[] files = file.listFiles();
+ for (int i = 0; files != null && i < files.length; i++) {
+ delete(files[i]);
+ }
+ file.delete();
+ }
+
+ private static String getUUID(Node node) {
+ return ((NodeImpl) node).getNodeId().toString();
+ }
+
+ private static int indentCount = 0;
+
+ private static void displayTree(Node node) throws RepositoryException {
+ StringBuffer indent = new StringBuffer();
+ for (int i=0; i < indentCount; i++) {
+ indent.append(" ");
+ }
+ log.debug(indent + node.getName() + " [" + getUUID(node) + "]");
+ NodeIterator nodes = node.getNodes();
+ indentCount++;
+ while (nodes.hasNext()) {
+ displayTree(nodes.nextNode());
+ }
+ indentCount--;
+ }
+
+ //-------------------------------------------------------------------< repository setup >
+
+ private final static File DIRECTORY = createTempDir("jackrabbit", "test");
+
+ private final static String PROTOCOL = "jdbc:derby:";
+
+ private final static String DB_PATH = "/db/itemState";
+
+ private final static String VERSIONING_DB_PATH = "/version/db/itemState";
+
+ private static AdminRepositoryImpl repository = createRepository();
+
+ private static AdminRepositoryImpl createRepository() {
+ log.debug("Start repo at '" + DIRECTORY.getPath() + "'...");
+
+ RepositoryConf conf = new RepositoryConf();
+ // ensure correct derby config
+ PersistenceManagerConf pmc = conf.getWorkspaceConfTemplate().getPersistenceManagerConf();
+ pmc.setParameter("url", PROTOCOL + "${wsp.home}" + DB_PATH + ";create=true");
+
+ pmc = conf.getVersioningConf().getPersistenceManagerConf();
+ pmc.setParameter("url", PROTOCOL + "${rep.home}" + VERSIONING_DB_PATH + ";create=true");
+ pmc.setParameter("schemaObjectPrefix", "VERSION_");
+ try {
+ RepositoryConfig config = conf.createConfig(DIRECTORY.getPath());
+ config.init();
+ return new AdminRepositoryImpl(config);
+ } catch (Exception e) {
+ fail("Could not create repository for test: " + e);
+ return null;
+ }
+ }
+
+ private Session login(String workspace) throws RepositoryException {
+ return repository.login(new SimpleCredentials("admin", "admin".toCharArray()), workspace);
+ }
+
+ private static void deleteRepository() {
+ log.debug("Shutdown repo...");
+ repository.shutdown();
+ delete(DIRECTORY);
+ }
+
+ /**
+ * <code>AdminRepositoryImpl</code> makes <code>checkConsistency()</code> method
+ * in <code>RepositoryImpl</code> accessible.
+ */
+ private static class AdminRepositoryImpl extends RepositoryImpl {
+
+ public AdminRepositoryImpl(RepositoryConfig config) throws RepositoryException {
+ super(config);
+ }
+
+ // make protected method in RepositoryImpl public
+ public void checkConsistency(String workspaceName, String[] uuids,
+ boolean recursive, boolean fix) throws IllegalStateException,
+ NoSuchWorkspaceException, RepositoryException {
+ super.checkConsistency(workspaceName, uuids, recursive, fix);
+ }
+ }
+
+ //-------------------------------------------------------------------< helper methods >
+
+ private Connection getDerbyConnection(String workspace) throws SQLException {
+ Connection conn = null;
+ Properties props = new Properties();
+ props.put("user", "");
+ props.put("password", "");
+
+ if (JCR_SYSTEM.equals(workspace)) {
+ conn = DriverManager.getConnection(PROTOCOL + DIRECTORY.getPath() + VERSIONING_DB_PATH, props);
+ log.debug(conn.getMetaData().getURL());
+ } else {
+ String basePath = DIRECTORY.getPath() + File.separatorChar + "workspaces" + File.separatorChar;
+ conn = DriverManager.getConnection(PROTOCOL + basePath + workspace + DB_PATH, props);
+ }
+
+ conn.setAutoCommit(false);
+ return conn;
+ }
+
+ private void setUUIDParams(PreparedStatement s, int startIndex, UUID uuid) throws SQLException {
+ s.setLong(startIndex, uuid.getMostSignificantBits());
+ s.setLong(startIndex+1, uuid.getLeastSignificantBits());
+ }
+
+ private void deleteNodeBundle(Connection conn, String workspace, String uuid) throws SQLException {
+ PreparedStatement s = conn.prepareStatement(
+ "delete from " + workspace.toUpperCase() + "_BUNDLE where NODE_ID_HI = ? and NODE_ID_LO = ?");
+ setUUIDParams(s, 1, new UUID(uuid));
+ s.execute();
+ }
+
+ private byte[] readNodeBundleBlob(Connection conn, String workspace, String uuid) throws SQLException {
+ PreparedStatement s = conn.prepareStatement(
+ "select BUNDLE_DATA from " + workspace.toUpperCase() + "_BUNDLE where NODE_ID_HI = ? and NODE_ID_LO = ?");
+ setUUIDParams(s, 1, new UUID(uuid));
+ ResultSet rs = s.executeQuery();
+ if (rs.next()) {
+ return rs.getBytes(1);
+ }
+ return null;
+ }
+
+ private void writeNodeBundleBlob(Connection conn, String workspace, String uuid, byte[] bytes) throws SQLException {
+ PreparedStatement s = conn.prepareStatement(
+ "update " + workspace.toUpperCase() + "_BUNDLE set BUNDLE_DATA = ? where NODE_ID_HI = ? and NODE_ID_LO = ?");
+ s.setBytes(1, bytes);
+ setUUIDParams(s, 2, new UUID(uuid));
+ s.execute();
+ }
+
+ //-------------------------------------------------------------------< test data setup >
+
+ private final static String WORKSPACE = "default";
+
+ private static final String JCR_SYSTEM = "jcr:system";
+
+ private static String testNodeUUID;
+
+ private static String missingChildUUID;
+
+ private static String missingGrandChildUUID;
+
+ private static String missingParentUUID;
+
+ private static String clearedNodeUUID;
+
+ private static String brokenNodeUUID;
+
+ private void setupWorkspaceTestData() throws Exception {
+ Session session = login(WORKSPACE);
+
+ // prepare some parent-child relationships
+
+ // nested tree where children will get lost
+ Node fruits = session.getRootNode().addNode("fruits");
+ Node apple = fruits.addNode("apple");
+ Node bananas = fruits.addNode("bananas");
+ Node melons = fruits.addNode("melons");
+ melons.addNode("bittermelon");
+ Node watermelon = melons.addNode("watermelon");
+ Node honeydew = melons.addNode("honeydew");
+
+ // separate tree where the parent will get lost
+ Node vegetables = session.getRootNode().addNode("vegetables");
+ vegetables.addNode("cucumber");
+
+ testNodeUUID = getUUID(fruits);
+ missingChildUUID = getUUID(bananas);
+ missingGrandChildUUID = getUUID(watermelon);
+ missingParentUUID = getUUID(vegetables);
+ clearedNodeUUID = getUUID(honeydew);
+ brokenNodeUUID = getUUID(apple);
+
+ session.save();
+
+ Connection conn = getDerbyConnection(WORKSPACE);
+
+ // now delete/modify some bundles directly in the database
+ log.debug("delete parent " + missingParentUUID);
+ deleteNodeBundle(conn, WORKSPACE, missingParentUUID);
+
+ log.debug("delete child " + missingChildUUID);
+ deleteNodeBundle(conn, WORKSPACE, missingChildUUID);
+ log.debug("delete grand child " + missingGrandChildUUID);
+ deleteNodeBundle(conn, WORKSPACE, missingGrandChildUUID);
+
+ log.debug("clear node " + clearedNodeUUID);
+ // makes the bundle invalid, ie. BundleBinding.checkBundle() will throw an exception
+ writeNodeBundleBlob(conn, WORKSPACE, clearedNodeUUID, new byte[10]);
+
+ log.debug("break node " + brokenNodeUUID);
+ byte[] bytes = readNodeBundleBlob(conn, WORKSPACE, brokenNodeUUID);
+ // makes the bundle invalid, ie. BundleBinding.checkBundle() will return false (but won't throw an exception)
+ bytes[25] = 1;
+ writeNodeBundleBlob(conn, WORKSPACE, brokenNodeUUID, bytes);
+
+ conn.commit();
+ conn.close();
+ }
+
+
+ private static String brokenVersion;
+
+ private void setupVersioningTestData() throws Exception {
+ Session session = login(WORKSPACE);
+ // create a new node + first version
+ Node node = session.getRootNode().addNode("food");
+ node.addMixin("mix:versionable");
+ node.addNode("values").setProperty("kcal", 300);
+ session.save();
+ node.checkin();
+
+ // create a second version
+ node.checkout();
+ node.getNode("values").setProperty("kcal", 1234);
+ session.save();
+ node.checkin();
+
+ // create a third version
+ node.checkout();
+ node.getNode("values").setProperty("kcal", 5000);
+ session.save();
+ node.checkin();
+
+ displayTree(session.getRootNode().getNode("jcr:system/jcr:versionStorage"));
+
+ brokenVersion = "1.1";
+ Version v = node.getVersionHistory().getVersion(brokenVersion);
+ String missingNodeUUID = getUUID(v.getNode("jcr:frozenNode").getNode("values"));
+
+ Connection conn = getDerbyConnection(JCR_SYSTEM);
+
+ log.debug("delete node " + missingNodeUUID);
+ deleteNodeBundle(conn, "version", missingNodeUUID);
+
+ conn.commit();
+ conn.close();
+ }
+
+ private void assertRepositoryException(Node rootNode, String path) {
+ try {
+ rootNode.getNode(path);
+ } catch (PathNotFoundException e) {
+ fail("JCR-1428: no need for a consistencyFix then...");
+ } catch (RepositoryException e) {
+ // expected
+ }
+ }
+
+ private void assertPathNotFoundException(Node rootNode, String path) {
+ try {
+ rootNode.getNode(path);
+ } catch (PathNotFoundException e) {
+ // expewcted
+ } catch (RepositoryException e) {
+ log.error("JCR-1428: fix in consistencyCheck did not work", e);
+ fail("JCR-1428: fix in consistencyCheck did not work: " + e);
+ }
+ }
+
+ //-------------------------------------------------------------------< tests >
+
+ /**
+ * To verify the check, one has to manually look at the error log
+ * therefore run this test with these log4j settings:
+ *
+ * for looking at bundle errors only:
+ * log4j.rootLogger=ERROR, file
+ * log4j.logger.org.apache.jackrabbit.core=ERROR, stdout
+ * log4j.logger.org.apache.jackrabbit.test=DEBUG
+ *
+ * for full details on checked bundles, add this:
+ * log4j.logger.org.apache.jackrabbit.core.persistence.bundle.util.BundleBinding=INFO
+ *
+ * for details on fixing, add this:
+ * log4j.logger.org.apache.jackrabbit.core.persistence.bundle.BundleDbPersistenceManager=INFO
+ *
+ */
+ public void testConsistencyCheck() throws Exception {
+ // 1. test, init data
+ setupWorkspaceTestData();
+
+ log.debug("================================================ Checking workspace " + WORKSPACE + "...");
+ repository.checkConsistency(WORKSPACE, null, false, false);
+
+ log.debug("================================================ Checking workspace " + WORKSPACE + ", single node...");
+ repository.checkConsistency(WORKSPACE, new String[] { testNodeUUID, missingParentUUID }, false, false);
+
+ log.debug("================================================ Checking workspace " + WORKSPACE + ", single node, recursively...");
+ repository.checkConsistency(WORKSPACE, new String[] { testNodeUUID }, true, false);
+ }
+
+ public void testConsistencyFix() throws Exception {
+ // ensure all item state caches are empty
+ repository.shutdown();
+ repository = createRepository();
+
+ Session session;
+
+ // tests only the erroneous behaviour when bundles are missing
+ session = login(WORKSPACE);
+ assertRepositoryException(session.getRootNode(), "fruits/bananas");
+ assertRepositoryException(session.getRootNode(), "fruits/melons/watermelon");
+
+ log.debug("================================================ Checking and fixing workspace " + WORKSPACE + "...");
+ repository.checkConsistency(WORKSPACE, null, false, true);
+
+ // check the log here, it should no longer include any "Fixing bundle..." messages
+ log.debug("================================================ Rechecking workspace " + WORKSPACE + "...");
+ repository.checkConsistency(WORKSPACE, null, false, false);
+
+ repository.shutdown();
+ repository = createRepository();
+
+ session = login(WORKSPACE);
+ assertPathNotFoundException(session.getRootNode(), "fruits/bananas");
+ assertPathNotFoundException(session.getRootNode(), "fruits/melons/watermelon");
+ }
+
+ public void testConsistencyCheckVersioning() throws Exception {
+ log.debug(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> versioning storage...");
+ // 1. versioning test, setup data
+ setupVersioningTestData();
+
+ log.debug("================================================ Checking " + JCR_SYSTEM + "...");
+ repository.checkConsistency(JCR_SYSTEM, null, false, false);
+ }
+
+ public void testConsistencyFixVersioning() throws Exception {
+ // ensure all item state caches are empty
+ repository.shutdown();
+ repository = createRepository();
+
+ Session session = login(WORKSPACE);
+
+ // for debugging
+ //displayTree(session.getRootNode().getNode("jcr:system/jcr:versionStorage"));
+
+ Node node = session.getRootNode().getNode("food");
+ assertRepositoryException(node.getVersionHistory().getVersion(brokenVersion), "jcr:frozenNode/values");
+
+ log.debug("================================================ Checking and fixing " + JCR_SYSTEM + "...");
+ repository.checkConsistency(JCR_SYSTEM, null, false, true);
+
+ // check the log here, it should no longer include any "Fixing bundle..." messages
+ log.debug("================================================ Rechecking " + JCR_SYSTEM + "...");
+ repository.checkConsistency(JCR_SYSTEM, null, false, false);
+
+ repository.shutdown();
+ repository = createRepository();
+
+ session = login(WORKSPACE);
+ node = session.getRootNode().getNode("food");
+ assertPathNotFoundException(node.getVersionHistory().getVersion(brokenVersion), "jcr:frozenNode/values");
+ }
+
+ public void testRepositoryShutdown() {
+ // last test, stop (not necessarily needed, but cleans up file system)
+ //deleteRepository();
+ }
+}