/*
 * File   : $Source: /usr/local/cvs/opencms/test/org/opencms/file/TestUndoChanges.java,v $
 * Date   : $Date: 2008-04-04 07:42:03 $
 * Version: $Revision: 1.26 $
 *
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.file;

import org.opencms.file.types.CmsResourceTypeFolder;
import org.opencms.file.types.CmsResourceTypePlain;
import org.opencms.lock.CmsLockType;
import org.opencms.main.CmsException;
import org.opencms.main.OpenCms;
import org.opencms.test.OpenCmsTestCase;
import org.opencms.test.OpenCmsTestProperties;
import org.opencms.test.OpenCmsTestResourceFilter;
import org.opencms.test.OpenCmsTestResourceStorage;
import org.opencms.util.CmsStringUtil;

import java.util.Iterator;
import java.util.List;

import junit.extensions.TestSetup;
import junit.framework.Test;
import junit.framework.TestSuite;

/**
 * Unit test for the "undoChanges" method of the CmsObject.<p>
 * 
 * @author Michael Emmerich 
 * @version $Revision: 1.26 $
 */
public class TestUndoChanges extends OpenCmsTestCase {

    /**
     * Default JUnit constructor.<p>
     * 
     * @param arg0 JUnit parameters
     */
    public TestUndoChanges(String arg0) {

        super(arg0);
    }

    /**
     * Test suite for this test class.<p>
     * 
     * @return the test suite
     */
    public static Test suite() {

        OpenCmsTestProperties.initialize(org.opencms.test.AllTests.TEST_PROPERTIES_PATH);

        TestSuite suite = new TestSuite();
        suite.setName(TestUndoChanges.class.getName());

        suite.addTest(new TestUndoChanges("testUndoChangesResource"));
        suite.addTest(new TestUndoChanges("testUndoChangesOnNewResource"));
        suite.addTest(new TestUndoChanges("testUndoChangesFolder"));
        suite.addTest(new TestUndoChanges("testUndoChangesFolderRecursive"));
        suite.addTest(new TestUndoChanges("testUndoChangesAfterCopyNewOverDeleted"));
        suite.addTest(new TestUndoChanges("testUndoChangesAfterCopySiblingOverDeleted"));
        suite.addTest(new TestUndoChanges("testUndoChangesWithAce"));
        suite.addTest(new TestUndoChanges("testUndoChangesSharedProperty"));
        suite.addTest(new TestUndoChanges("testUndoChangesMove"));
        suite.addTest(new TestUndoChanges("testUndoChangesMoveContent"));
        suite.addTest(new TestUndoChanges("testUndoChangesMovedFolder"));
        suite.addTest(new TestUndoChanges("testUndoChangesMovedFolderAfterEdit"));
        suite.addTest(new TestUndoChanges("testUndoChangesMovedFolderNewFile"));
        suite.addTest(new TestUndoChanges("testUndoChangesSubfolderAfterMoving"));
        suite.addTest(new TestUndoChanges("testUndoChangesMovedFileAfterEdit"));
        suite.addTest(new TestUndoChanges("testUndoChangesScenario1"));

        TestSetup wrapper = new TestSetup(suite) {

            protected void setUp() {

                setupOpenCms("simpletest", "/sites/default/");
            }

            protected void tearDown() {

                removeOpenCms();
            }
        };

        return wrapper;
    }

    /**
     * Test the touch method to touch a single resource.<p>
     * @param tc the OpenCmsTestCase
     * @param cms the CmsObject
     * @param resource1 the resource to touch
     * @throws Throwable if something goes wrong
     */
    public static void undoChanges(OpenCmsTestCase tc, CmsObject cms, String resource1) throws Throwable {

        // create a global storage and store the resource
        tc.createStorage("undoChanges");
        tc.switchStorage("undoChanges");
        tc.storeResources(cms, resource1);
        tc.switchStorage(OpenCmsTestResourceStorage.DEFAULT_STORAGE);

        // now do a touch on the resource
        TestTouch.touchResource(tc, cms, resource1);

        // change a property
        CmsProperty property1 = new CmsProperty("Title", "undoChanges2", null);
        TestProperty.writeProperty(tc, cms, resource1, property1);

        // now undo everything
        cms.lockResource(resource1);
        cms.undoChanges(resource1, CmsResource.UNDO_CONTENT);
        cms.unlockResource(resource1);

        tc.switchStorage("undoChanges");

        // now evaluate the result
        tc.assertFilter(cms, resource1, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        // project must be current project
        tc.assertProject(cms, resource1, cms.getRequestContext().currentProject());
    }

    /**
     *  Test undoChanges method to a single folder.<p>
     * @param tc the OpenCmsTestCase
     * @param cms the CmsObject
     * @param resource1 the resource to touch
     * @throws Throwable if something goes wrong
     */
    public static void undoChangesFolder(OpenCmsTestCase tc, CmsObject cms, String resource1) throws Throwable {

        // create a global storage and store the resource
        tc.createStorage("undoChanges");
        tc.switchStorage("undoChanges");
        tc.storeResources(cms, resource1);
        tc.switchStorage(OpenCmsTestResourceStorage.DEFAULT_STORAGE);

        long timestamp = System.currentTimeMillis();

        // change a property
        CmsProperty property1 = new CmsProperty("Title", "undoChanges", null);
        TestProperty.writeProperty(tc, cms, resource1, property1);

        // change the property on all subresources
        List subresources = cms.readResources(resource1, CmsResourceFilter.ALL);
        Iterator i = subresources.iterator();
        while (i.hasNext()) {
            CmsResource res = (CmsResource)i.next();
            String resName = cms.getSitePath(res);
            TestProperty.writeProperty(tc, cms, resName, property1);
        }

        // now undo everything
        cms.lockResource(resource1);
        cms.undoChanges(resource1, CmsResource.UNDO_CONTENT);
        cms.unlockResource(resource1);

        tc.switchStorage("undoChanges");

        // now evaluate the result, the folder must be unchanged now
        tc.assertFilter(cms, resource1, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        // project must be current project
        tc.assertProject(cms, resource1, cms.getRequestContext().currentProject());

        // all resources within the folder must keep their changes
        Iterator j = subresources.iterator();
        while (j.hasNext()) {
            CmsResource res = (CmsResource)j.next();
            String resName = cms.getSitePath(res);
            tc.assertFilter(cms, resName, OpenCmsTestResourceFilter.FILTER_WRITEPROPERTY);
            // project must be current project
            tc.assertProject(cms, resName, cms.getRequestContext().currentProject());
            // state must be "changed"
            tc.assertState(cms, resName, tc.getPreCalculatedState(resource1));
            // date last modified must be after the test timestamp
            tc.assertDateLastModifiedAfter(cms, resName, timestamp);
            // the user last modified must be the current user
            tc.assertUserLastModified(cms, resName, cms.getRequestContext().currentUser());
            // the property must have the new value
            tc.assertPropertyChanged(cms, resName, property1);
        }
    }

    /**
     * Test undoChanges method to a single folder and all resources within the folder.<p>
     * @param tc the OpenCmsTestCase
     * @param cms the CmsObject
     * @param resource1 the resource to touch
     * @throws Throwable if something goes wrong
     */
    public static void undoChangesFolderRecursive(OpenCmsTestCase tc, CmsObject cms, String resource1) throws Throwable {

        // create a global storage and store the resource
        tc.createStorage("undoChanges");
        tc.switchStorage("undoChanges");
        tc.storeResources(cms, resource1);
        tc.switchStorage(OpenCmsTestResourceStorage.DEFAULT_STORAGE);

        // change a property
        CmsProperty property1 = new CmsProperty("Title", "undoChanges", null);
        TestProperty.writeProperty(tc, cms, resource1, property1);

        // change the property on all subresources
        List subresources = cms.readResources(resource1, CmsResourceFilter.ALL);
        Iterator i = subresources.iterator();
        while (i.hasNext()) {
            CmsResource res = (CmsResource)i.next();
            String resName = cms.getSitePath(res);
            TestProperty.writeProperty(tc, cms, resName, property1);
        }

        // now undo everything
        cms.lockResource(resource1);
        cms.undoChanges(resource1, CmsResource.UNDO_CONTENT_RECURSIVE);
        cms.unlockResource(resource1);

        tc.switchStorage(OpenCmsTestResourceStorage.GLOBAL_STORAGE);

        // now evaluate the result, the folder must be unchanged now
        tc.assertFilter(cms, resource1, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        // project must be current project
        tc.assertProject(cms, resource1, cms.getRequestContext().currentProject());

        // all resources within the folder must  be unchanged now
        Iterator j = subresources.iterator();
        while (j.hasNext()) {
            CmsResource res = (CmsResource)j.next();
            String resName = cms.getSitePath(res);

            // now evaluate the result
            tc.assertFilter(cms, resName, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
            // project must be current project
            tc.assertProject(cms, resName, cms.getRequestContext().currentProject());
        }
    }

    /**
     * Tests undo changes after a resource was deleted and another 
     * resource was copied over the deleted file "as new".<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesAfterCopyNewOverDeleted() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undo changes after overwriting a deleted file with a new file");

        String source = "/folder1/page2.html";
        String destination = "/folder1/page1.html";

        storeResources(cms, source);
        storeResources(cms, destination);

        cms.lockResource(destination);

        // delete and owerwrite
        cms.deleteResource(destination, CmsResource.DELETE_PRESERVE_SIBLINGS);
        assertState(cms, destination, CmsResource.STATE_DELETED);

        cms.copyResource(source, destination, CmsResource.COPY_AS_NEW);

        // now undo all changes on the resource
        cms.undoChanges(destination, CmsResource.UNDO_CONTENT);

        // now ensure source and destionation are in the original state
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, destination, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);

        // publishing may reveal problems with the id's
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Tests undo changes of move operations as follows:<p>
     * 
     * having following start structure:
     * - (black) /folder
     * - (black) /folder/subfolder
     * - (black) /folder/subfolder/subsubfolder 
     * 
     * a. move /folder/subfolder to /subfolder
     * b. move /subfolder/subsubfolder to /subsubfolder
     * c. move /subfolder back to its starting location: /folder/subfolder
     * d. undo changes of move operation for /subsubfolder
     * 
     * to get following scenario:
     * - (black) /folder
     * - (red) /folder/subfolder
     * - (black) /folder/subfolder/subsubfolder 
     * 
     * e. undo changes of move operation for /folder/subfolder
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesScenario1() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undo changes after overwriting a deleted file with a new file");

        String folder = "folder";
        String subfolder = "subfolder";
        String subsubfolder = "subsubfolder";
        String file = "file";

        // create starting point
        cms.createResource(folder, CmsResourceTypeFolder.RESOURCE_TYPE_ID);
        cms.createResource(folder + "/" + file, CmsResourceTypePlain.getStaticTypeId());
        cms.createResource(folder + "/" + subfolder, CmsResourceTypeFolder.RESOURCE_TYPE_ID);
        cms.createResource(folder + "/" + subfolder + "/" + file, CmsResourceTypePlain.getStaticTypeId());
        cms.createResource(folder + "/" + subfolder + "/" + subsubfolder, CmsResourceTypeFolder.RESOURCE_TYPE_ID);
        cms.createResource(
            folder + "/" + subfolder + "/" + subsubfolder + "/" + file,
            CmsResourceTypePlain.getStaticTypeId());
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishResource(cms, folder);
        OpenCms.getPublishManager().waitWhileRunning();

        storeResources(cms, "/" + folder, true);

        // a. move /folder/subfolder to /subfolder
        cms.lockResource(folder + "/" + subfolder);
        cms.moveResource(folder + "/" + subfolder, "/" + subfolder);

        // b. move /subfolder/subsubfolder to /subsubfolder
        cms.lockResource(subfolder + "/" + subsubfolder);
        cms.moveResource(subfolder + "/" + subsubfolder, "/" + subsubfolder);

        // c. move /subfolder back to its starting location: /folder/subfolder
        cms.moveResource("/" + subfolder, folder + "/" + subfolder);

        // d. undo changes of move operation for /subsubfolder
        cms.undoChanges("/" + subsubfolder, CmsResource.UNDO_MOVE_CONTENT);

        // check intermediate state
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        assertFilter(cms, "/" + folder + "/", OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, "/" + folder + "/" + file, OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, "/" + folder + "/" + subfolder + "/", OpenCmsTestResourceFilter.FILTER_TOUCH);
        assertFilter(cms, "/" + folder + "/" + subfolder + "/" + file, OpenCmsTestResourceFilter.FILTER_TOUCH);
        assertFilter(
            cms,
            "/" + folder + "/" + subfolder + "/" + subsubfolder + "/",
            OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        assertFilter(
            cms,
            "/" + folder + "/" + subfolder + "/" + subsubfolder + "/" + file,
            OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);

        // e. undo changes of move operation for /folder/subfolder
        cms.lockResource(folder + "/" + subfolder);
        cms.undoChanges(folder + "/" + subfolder, CmsResource.UNDO_MOVE_CONTENT);

        // check final state, the same starting state
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        assertFilter(cms, "/" + folder + "/", OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, "/" + folder + "/" + file, OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, "/" + folder + "/" + subfolder + "/", OpenCmsTestResourceFilter.FILTER_MOVE_DESTINATION);
        assertFilter(
            cms,
            "/" + folder + "/" + subfolder + "/" + file,
            OpenCmsTestResourceFilter.FILTER_MOVE_DESTINATION);
        assertFilter(
            cms,
            "/" + folder + "/" + subfolder + "/" + subsubfolder + "/",
            OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        assertFilter(
            cms,
            "/" + folder + "/" + subfolder + "/" + subsubfolder + "/" + file,
            OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
    }

    /**
     * Tests undo changes after a resource was deleted and another 
     * resource was copied over the deleted file "as sibling".<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesAfterCopySiblingOverDeleted() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undo changes after overwriting a deleted file with a sibling");

        String source = "/folder1/page2.html";
        String destination = "/folder1/page1.html";

        storeResources(cms, source);
        storeResources(cms, destination);

        cms.lockResource(destination);

        // delete and owerwrite with a sibling
        cms.deleteResource(destination, CmsResource.DELETE_PRESERVE_SIBLINGS);
        assertState(cms, destination, CmsResource.STATE_DELETED);

        cms.copyResource(source, destination, CmsResource.COPY_AS_SIBLING);

        // now undo all changes on the resource
        cms.undoChanges(destination, CmsResource.UNDO_CONTENT);

        // now ensure source and destionation are in the original state
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        assertFilter(cms, destination, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);

        // publishing may reveal problems with the id's
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Test undoChanges method to a single folder.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesFolder() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undoChanges on a folder without recursion");
        undoChangesFolder(this, cms, "/folder2/");
    }

    /**
     * Test undoChanges method to a single folder.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesFolderRecursive() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undoChanges on a folder _with_ recursion");
        undoChangesFolderRecursive(this, cms, "/folder1/");
    }

    /**
     * Tests undo changes for the move operation only.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesMove() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undo changes for the move operation only");

        String source = "/folder1/";
        String destination = "/folder1_undo2/";
        String sourceFile = "page1.html";
        String newFile = "filenew.html";

        storeResources(cms, source, true);

        // move
        cms.lockResource(source);
        cms.moveResource(source, destination);

        // edit
        cms.writePropertyObject(destination + sourceFile, new CmsProperty("test property", "a", "b", true));

        // create new file
        cms.createResource(destination + newFile, CmsResourceTypePlain.getStaticTypeId());
        storeResources(cms, destination + newFile);

        // now undo all changes on the folder
        cms.undoChanges(destination, CmsResource.UNDO_MOVE_CONTENT);

        // now ensure source and destination are in the original state
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        // project must be current project
        assertProject(cms, source, cms.getRequestContext().currentProject());

        setMapping(source + newFile, destination + newFile);
        // test recursively
        Iterator subresources = cms.readResources(source, CmsResourceFilter.ALL).iterator();
        while (subresources.hasNext()) {
            CmsResource res = (CmsResource)subresources.next();
            String resName = cms.getSitePath(res);
            if (resName.equals(source + newFile)) {
                assertFilter(cms, resName, OpenCmsTestResourceFilter.FILTER_CREATE_RESOURCE);
            } else {
                assertFilter(cms, resName, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
            }
            // project must be current project
            assertProject(cms, resName, cms.getRequestContext().currentProject());
        }
        assertTrue(cms.existsResource(source, CmsResourceFilter.ALL));
        assertTrue(cms.existsResource(source + sourceFile));
        assertTrue(cms.existsResource(source + newFile));
        assertFalse(cms.existsResource(destination, CmsResourceFilter.ALL));

        cms.deleteResource(source + newFile, CmsResource.DELETE_REMOVE_SIBLINGS);
        // publishing may reveal problems with the id's
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Tests undo changes for the content changes only.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesMoveContent() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undo changes for the content changes only");

        String source = "/folder1/";
        String destination = "/folder1_undo2/";
        String sourceFile = "page1.html";
        String newFile = "filenew.html";

        storeResources(cms, source, true);

        // move
        cms.lockResource(source);
        cms.moveResource(source, destination);

        // edit
        cms.writePropertyObject(destination + sourceFile, new CmsProperty("test property", "a", "b", true));

        // create new file
        cms.createResource(destination + newFile, CmsResourceTypePlain.getStaticTypeId());

        // now undo all content changes on the folder
        cms.undoChanges(destination, CmsResource.UNDO_CONTENT_RECURSIVE);

        setMapping(destination, source);
        // now ensure source and destionation are in the original state
        assertFilter(cms, destination, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_CONTENT);
        // project must be current project
        assertProject(cms, destination, cms.getRequestContext().currentProject());
        // test recursively
        Iterator subresources = cms.readResources(destination, CmsResourceFilter.ALL).iterator();
        while (subresources.hasNext()) {
            CmsResource res = (CmsResource)subresources.next();
            String resName = cms.getSitePath(res);
            if (resName.equals(destination + newFile)) {
                assertState(cms, resName, CmsResource.STATE_NEW);
            } else {
                setMapping(resName, CmsStringUtil.substitute(resName, destination, source));
                assertFilter(cms, resName, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_CONTENT);
            }
            // project must be current project
            assertProject(cms, resName, cms.getRequestContext().currentProject());
        }
        assertTrue(cms.existsResource(destination));
        assertTrue(cms.existsResource(destination + sourceFile));
        assertTrue(cms.existsResource(destination + newFile));
        assertFalse(cms.existsResource(source, CmsResourceFilter.ALL));

        // now undo move operation on the folder
        cms.undoChanges(destination, CmsResource.UNDO_MOVE_CONTENT);

        resetMapping();
        // now ensure source and destionation are in the original state
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        // project must be current project
        assertProject(cms, source, cms.getRequestContext().currentProject());
        // test recursively
        subresources = cms.readResources(source, CmsResourceFilter.ALL).iterator();
        while (subresources.hasNext()) {
            CmsResource res = (CmsResource)subresources.next();
            String resName = cms.getSitePath(res);
            if (resName.equals(source + newFile)) {
                assertState(cms, resName, CmsResource.STATE_NEW);
            } else {
                assertFilter(cms, resName, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
            }
            // project must be current project
            assertProject(cms, resName, cms.getRequestContext().currentProject());
        }
        assertTrue(cms.existsResource(source));
        assertTrue(cms.existsResource(source + sourceFile));
        assertTrue(cms.existsResource(source + newFile));
        assertFalse(cms.existsResource(destination, CmsResourceFilter.ALL));

        cms.deleteResource(source + newFile, CmsResource.DELETE_REMOVE_SIBLINGS);
        // publishing may reveal problems with the id's
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Tests undo changes after a file was moved and edited.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesMovedFileAfterEdit() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undo changes after a file was moved and edited");

        String source = "/folder1/page1.html";
        String destination = "/folder1/page1_undo.html";

        storeResources(cms, source, true);

        // move
        cms.lockResource(source);
        cms.moveResource(source, destination);

        // edit
        cms.writePropertyObject(destination, new CmsProperty("test property", "a", "b", true));

        // now undo content changes
        cms.undoChanges(destination, CmsResource.UNDO_CONTENT_RECURSIVE);

        setMapping(destination, source);
        // now ensure destination is in the original state
        assertFilter(cms, destination, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_CONTENT);
        // project must be current project
        assertProject(cms, destination, cms.getRequestContext().currentProject());
        // file must still be moved
        assertFalse(cms.existsResource(source, CmsResourceFilter.ALL));

        // now undo move operation
        cms.undoChanges(destination, CmsResource.UNDO_MOVE_CONTENT_RECURSIVE);

        // now ensure source is in the original state
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        // file must be in source location
        assertFalse(cms.existsResource(destination, CmsResourceFilter.ALL));

        // publishing may reveal problems with the id's
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Tests undo changes after a folder was moved.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesMovedFolder() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undo changes after moving a folder");

        String source = "/folder1/";
        String destination = "/folder1_undo3/";

        storeResources(cms, source);

        cms.lockResource(source);
        // move
        cms.moveResource(source, destination);

        // now undo changes on the folder
        cms.undoChanges(destination, CmsResource.UNDO_MOVE_CONTENT_RECURSIVE);

        // now ensure source and destionation are in the original state
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        assertFalse(cms.existsResource(destination, CmsResourceFilter.ALL));

        // publishing may reveal problems with the id's
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Tests undo changes after a folder was moved and a file in the folder edited.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesMovedFolderAfterEdit() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undo changes after a folder was moved and a file in the folder edited");

        String source = "/folder1/";
        String destination = "/folder1_undo2/";
        String sourceFile = "page1.html";

        storeResources(cms, source, true);

        // move
        cms.lockResource(source);
        cms.moveResource(source, destination);

        // edit
        cms.writePropertyObject(destination + sourceFile, new CmsProperty("test property", "a", "b", true));

        // now undo all changes on the folder
        cms.undoChanges(destination, CmsResource.UNDO_MOVE_CONTENT_RECURSIVE);

        // now ensure source and destionation are in the original state
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        // project must be current project
        assertProject(cms, source, cms.getRequestContext().currentProject());
        // test recursively
        Iterator subresources = cms.readResources(source, CmsResourceFilter.ALL).iterator();
        while (subresources.hasNext()) {
            CmsResource res = (CmsResource)subresources.next();
            String resName = cms.getSitePath(res);
            // destination + sourceFile changes get lost
            assertFilter(cms, resName, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
            // project must be current project
            assertProject(cms, resName, cms.getRequestContext().currentProject());
        }
        assertFalse(cms.existsResource(destination, CmsResourceFilter.ALL));

        // publishing may reveal problems with the id's
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Tests undo changes after a folder was moved and a file in the folder edited.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesMovedFolderNewFile() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undo changes after a folder was moved and a file in the folder edited");

        String source = "/folder1/";
        String destination = "/folder1_undo2/";
        String newFile = "newfile.html";

        storeResources(cms, source, true);

        // move
        cms.lockResource(source);
        cms.moveResource(source, destination);

        // create new file
        cms.createResource(destination + newFile, CmsResourceTypePlain.getStaticTypeId());

        // now undo all changes on the folder
        cms.undoChanges(destination, CmsResource.UNDO_MOVE_CONTENT_RECURSIVE);

        // now ensure source and destionation are in the original state
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        // project must be current project
        assertProject(cms, source, cms.getRequestContext().currentProject());
        // test recursively
        Iterator subresources = cms.readResources(source, CmsResourceFilter.ALL).iterator();
        while (subresources.hasNext()) {
            CmsResource res = (CmsResource)subresources.next();
            String resName = cms.getSitePath(res);
            // newFile should still there
            if (resName.equals(source + newFile)) {
                assertState(cms, source + newFile, CmsResource.STATE_NEW);
            } else {
                assertFilter(cms, resName, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
                // project must be current project
                assertProject(cms, resName, cms.getRequestContext().currentProject());
            }
        }
        assertFalse(cms.existsResource(destination));
        assertTrue(cms.existsResource(source + newFile));

        // publishing may reveal problems with the id's
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Tests undo changes on a new resource, this must lead to an exception!<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesOnNewResource() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing for exception when trying undo changes on a new resource");

        String source = "/types/new.html";

        // create a new, plain resource
        cms.createResource(source, CmsResourceTypePlain.getStaticTypeId());
        assertLock(cms, source, CmsLockType.EXCLUSIVE);

        try {
            cms.undoChanges(source, CmsResource.UNDO_CONTENT);
        } catch (CmsVfsException e) {
            if (!e.getMessageContainer().getKey().equals(org.opencms.db.Messages.ERR_UNDO_CHANGES_FOR_RESOURCE_1)) {
                fail("Did not catch expected exception trying undo changes on a new resource!");
            }
        }
    }

    /**
     * Test undoChanges method to a single file.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesResource() throws Throwable {

        CmsObject cms = getCmsObject();

        // this is the first test, so set up the global storage used for all other
        // tests        
        createStorage(OpenCmsTestResourceStorage.GLOBAL_STORAGE);
        switchStorage(OpenCmsTestResourceStorage.GLOBAL_STORAGE);
        storeResources(cms, "/");
        switchStorage(OpenCmsTestResourceStorage.DEFAULT_STORAGE);

        echo("Testing undoChanges on a file");
        undoChanges(this, cms, "/index.html");
    }

    /**
     * Tests undo changes after a folder was moved several times.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesSeveralMovedFolder() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undo changes after moving a folder several times");

        String source = "/folder1/";
        String destination = "/folder1_undo4/";
        String source2 = "/subfolder11/";
        String destination2 = "/subfolder11_undo/";

        cms.lockResource(source);
        // move
        cms.moveResource(source, destination);
        // move sub folder
        cms.moveResource(destination + source2, destination2);

        // now try to undo changes on the sub folder
        try {
            cms.undoChanges(destination2, CmsResource.UNDO_MOVE_CONTENT_RECURSIVE);
            fail("undo changes should fail, since a parent folder is missing");
        } catch (Exception e) {
            // ok
        }

        // undo changes of parent folder
        cms.undoChanges(destination, CmsResource.UNDO_MOVE_CONTENT_RECURSIVE);
        assertFalse(cms.existsResource(destination, CmsResourceFilter.ALL));
        assertTrue(cms.existsResource(source));
        // subfolder should still be moved
        assertFalse(cms.existsResource(destination + source2, CmsResourceFilter.ALL));
        assertTrue(cms.existsResource(destination2));

        // undo changes of sub folder
        cms.undoChanges(destination2, CmsResource.UNDO_MOVE_CONTENT_RECURSIVE);
        assertTrue(cms.existsResource(source + source2));
        assertFalse(cms.existsResource(destination2, CmsResourceFilter.ALL));

        // publishing may reveal problems with the id's
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Test undoChanges method to a resource with an ace.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesSharedProperty() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undoChanges on shared property");

        // create the files
        String file = "/a";
        cms.createResource(file, CmsResourceTypePlain.getStaticTypeId());
        // publish the project
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();

        String sibling = "/b";
        TestSiblings.createSibling(this, cms, file, sibling);
        // write a persistent no-shared property to test with
        CmsProperty property = new CmsProperty(CmsPropertyDefinition.PROPERTY_NAVTEXT, "undoChanges navText", null);
        cms.writePropertyObject(sibling, property);
        // write a persistent shared property to test with
        CmsProperty property1 = new CmsProperty(
            CmsPropertyDefinition.PROPERTY_DESCRIPTION,
            null,
            "undoChanges description");
        cms.writePropertyObject(sibling, property1);
        // publish the project
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();

        // create a global storage and store the resource
        createStorage("undoChanges");
        switchStorage("undoChanges");
        storeResources(cms, file);
        storeResources(cms, sibling);
        switchStorage(OpenCmsTestResourceStorage.DEFAULT_STORAGE);

        // change a shared property
        CmsProperty property2 = new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, null, "undoChanges title");
        cms.lockResource(file);
        cms.writePropertyObject(file, property2);
        cms.unlockResource(file);
        //TestProperty.writeProperty(this, cms, file1, property);

        // now undo everything
        cms.lockResource(file);
        cms.undoChanges(file, CmsResource.UNDO_CONTENT);
        cms.unlockResource(file);

        switchStorage("undoChanges");

        // now evaluate the result
        assertFilter(cms, file, OpenCmsTestResourceFilter.FILTER_UNDOCHANGES_ALL);
        // project must be current project
        assertProject(cms, file, cms.getRequestContext().currentProject());

        // publishing may reveal problems with the id's
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Test undoChanges method to a sub folder after moving a whole folder.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesSubfolderAfterMoving() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undoChanges on a sub folder after moving a whole folder");

        String source = "/folder1/";
        String destination = "/folder1_undo1/";
        String undo = "subfolder11/";

        cms.lockResource(source);

        // move 
        cms.moveResource(source, destination);

        // now undo all changes on the subfolder
        try {
            cms.undoChanges(destination + undo, CmsResource.UNDO_MOVE_CONTENT_RECURSIVE);
            fail("undoing move of subfolder must not be allowed");
        } catch (CmsException e) {
            // expected
        }

        cms.undoChanges(destination, CmsResource.UNDO_MOVE_CONTENT_RECURSIVE);
        // publishing may reveal problems with the id's
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Test undoChanges method to a resource with an ace.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testUndoChangesWithAce() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing undoChanges on a resource with an ACE");
        undoChanges(this, cms, "/folder2/index.html");
    }

}
