/*
 * File   : $Source: /usr/local/cvs/opencms/test/org/opencms/file/TestPublishing.java,v $
 * Date   : $Date: 2008-02-27 12:05:35 $
 * Version: $Revision: 1.30 $
 *
 * 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.db.CmsPublishList;
import org.opencms.db.CmsPublishedResource;
import org.opencms.db.CmsResourceState;
import org.opencms.file.history.CmsHistoryFile;
import org.opencms.file.types.CmsResourceTypeFolder;
import org.opencms.file.types.CmsResourceTypeImage;
import org.opencms.file.types.CmsResourceTypePlain;
import org.opencms.file.types.CmsResourceTypeXmlPage;
import org.opencms.i18n.CmsEncoder;
import org.opencms.lock.CmsLockFilter;
import org.opencms.lock.CmsLockType;
import org.opencms.main.CmsException;
import org.opencms.main.CmsMultiException;
import org.opencms.main.OpenCms;
import org.opencms.publish.CmsPublishJobFinished;
import org.opencms.report.CmsShellReport;
import org.opencms.test.OpenCmsTestCase;
import org.opencms.test.OpenCmsTestLogAppender;
import org.opencms.test.OpenCmsTestProperties;
import org.opencms.test.OpenCmsTestResourceFilter;
import org.opencms.xml.page.CmsXmlPage;
import org.opencms.xml.page.CmsXmlPageFactory;

import java.util.Collections;
import java.util.List;
import java.util.Locale;

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

/**
 * Unit tests for OpenCms publishing.<p>
 * 
 * @author Michael Emmerich 
 * 
 * @version $Revision: 1.30 $
 */
public class TestPublishing extends OpenCmsTestCase {

    /**
     * Default JUnit constructor.<p>
     * 
     * @param arg0 JUnit parameters
     */
    public TestPublishing(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(TestPublishing.class.getName());

        suite.addTest(new TestPublishing("testPublishQueueIssue1"));
        suite.addTest(new TestPublishing("testPublishQueueIssue2"));
        suite.addTest(new TestPublishing("testPublishQueueIssue3"));
        suite.addTest(new TestPublishing("testPublishResourceGalore"));
        suite.addTest(new TestPublishing("testPublishResourceWithRelatedGalore"));
        suite.addTest(new TestPublishing("testPublishProjectGalore"));
        suite.addTest(new TestPublishing("testPublishStructureProperty"));
        suite.addTest(new TestPublishing("testPublishResourceProperty"));
        suite.addTest(new TestPublishing("testPublishSiblings"));
        suite.addTest(new TestPublishing("testPublishCopySibling"));
        suite.addTest(new TestPublishing("testPublishNewFiles"));
        suite.addTest(new TestPublishing("testPublishNewFilesInNewFolder"));
        suite.addTest(new TestPublishing("testPublishChangedFiles"));
        suite.addTest(new TestPublishing("testPublishLockedFiles"));
        suite.addTest(new TestPublishing("testPublishFolderWithLockedFiles"));
        suite.addTest(new TestPublishing("testPublishDeletedFiles"));
        suite.addTest(new TestPublishing("testPublishProjectLastmodified"));
        suite.addTest(new TestPublishing("testPublishProjectLastmodifiedFolder"));
        suite.addTest(new TestPublishing("testPublishTemporaryProject"));
        suite.addTest(new TestPublishing("testPublishMovedFiles"));
        suite.addTest(new TestPublishing("testPublishRelatedFiles"));
        suite.addTest(new TestPublishing("testPublishRelatedFilesInFolder"));
        suite.addTest(new TestPublishing("testPublishRelatedFilesInNewFolder"));
        suite.addTest(new TestPublishing("testPublishContentDate"));
        suite.addTest(new TestPublishing("testPublishDeletedSiblings"));
        suite.addTest(new TestPublishing("testPublishDeletedSiblings2"));
        suite.addTest(new TestPublishing("testPublishReplacedFile"));

        TestSetup wrapper = new TestSetup(suite) {

            protected void setUp() {

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

            protected void tearDown() {

                removeOpenCms();
            }
        };

        return wrapper;
    }

    /**
     * Test an issue with the publish queue when trying to publish a folder that has already a subresource that is being published.<p>
     * 
     * SECOND TRY:
     * - modify and lock a file, publish the file
     * - lock the folder, modify a second file, publish the folder
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishQueueIssue2() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing a folder with a subresource in the publish queue, second try");

        // stop the publish engine to ensure that the file will not be published before we try to publish the folder
        OpenCms.getPublishManager().stopPublishing();

        // touch and publish a file in a folder
        String folder = "/folder1/subfolder11/subsubfolder111/";
        String file = folder + "image.gif";
        cms.lockResource(file);
        cms.setDateLastModified(file, System.currentTimeMillis(), false);
        OpenCms.getPublishManager().publishResource(cms, file);

        // check the publish queue
        assertEquals(1, OpenCms.getPublishManager().getPublishQueue().size());

        // touch a second file and publish the folder
        String file2 = folder + "text.txt";
        cms.lockResource(folder);
        cms.setDateLastModified(file2, System.currentTimeMillis(), false);
        OpenCms.getPublishManager().publishResource(cms, folder);

        // check the publish queue
        assertEquals(2, OpenCms.getPublishManager().getPublishQueue().size());

        // start the publish engine
        OpenCms.getPublishManager().startPublishing();
        OpenCms.getPublishManager().waitWhileRunning();

        // check the publish queue
        assertEquals(0, OpenCms.getPublishManager().getPublishQueue().size());

        // check publish reports
        List history = OpenCms.getPublishManager().getPublishHistory();
        CmsPublishJobFinished publishJob = (CmsPublishJobFinished)history.get(history.size() - 2);
        String reportContents = new String(OpenCms.getPublishManager().getReportContents(publishJob));
        // check if the report states that the destination was published successfully
        if (!reportContents.matches("(?s)" + "(.*)" + file + "(.*)" + "o.k." + "(.*)")) {
            System.err.println(reportContents);
            fail("publish report contains errors");
        }
        publishJob = (CmsPublishJobFinished)history.get(history.size() - 1);
        reportContents = new String(OpenCms.getPublishManager().getReportContents(publishJob));
        // check if the report states that the destination was published successfully
        if (!reportContents.matches("(?s)" + "(.*)" + file2 + "(.*)" + "o.k." + "(.*)")) {
            System.err.println(reportContents);
            fail("publish report contains errors");
        }

        cms.unlockResource(folder);
        // check the published files
        assertState(cms, file, CmsResourceState.STATE_UNCHANGED);
        assertLock(cms, file, CmsLockType.UNLOCKED);
        assertState(cms, file2, CmsResourceState.STATE_UNCHANGED);
        assertLock(cms, file2, CmsLockType.UNLOCKED);
        assertState(cms, folder, CmsResourceState.STATE_UNCHANGED);
        assertLock(cms, folder, CmsLockType.UNLOCKED);
    }

    /**
     * Test an issue with the publish queue when trying to publish a folder that has already a subresource that is being published.<p>
     * 
     * THIRD TRY:
     * - modify and lock a file, publish the file
     * - modify and lock the folder, publish the folder
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishQueueIssue3() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing a folder with a subresource in the publish queue, third try");

        // stop the publish engine to ensure that the file will not be published before we try to publish the folder
        OpenCms.getPublishManager().stopPublishing();

        // touch and publish a file in a folder
        String folder = "/folder1/subfolder11/subsubfolder111/";
        String file = folder + "image.gif";
        cms.lockResource(file);
        cms.setDateLastModified(file, System.currentTimeMillis(), false);
        OpenCms.getPublishManager().publishResource(cms, file);

        // check the publish queue
        assertEquals(1, OpenCms.getPublishManager().getPublishQueue().size());

        // touch a second file and publish the folder
        cms.lockResource(folder);
        String file2 = folder + "text.txt";
        cms.setDateLastModified(file2, System.currentTimeMillis(), false);
        cms.setDateLastModified(folder, System.currentTimeMillis(), false);
        OpenCms.getPublishManager().publishResource(cms, folder);

        // check the publish queue
        assertEquals(2, OpenCms.getPublishManager().getPublishQueue().size());

        // start the publish engine
        OpenCms.getPublishManager().startPublishing();
        OpenCms.getPublishManager().waitWhileRunning();

        // check the publish queue
        assertEquals(0, OpenCms.getPublishManager().getPublishQueue().size());

        // check publish reports
        List history = OpenCms.getPublishManager().getPublishHistory();
        CmsPublishJobFinished publishJob = (CmsPublishJobFinished)history.get(history.size() - 2);
        String reportContents = new String(OpenCms.getPublishManager().getReportContents(publishJob));
        // check if the report states that the destination was published successfully
        if (!reportContents.matches("(?s)" + "(.*)" + file + "(.*)" + "o.k." + "(.*)")) {
            System.err.println(reportContents);
            fail("publish report contains errors");
        }
        publishJob = (CmsPublishJobFinished)history.get(history.size() - 1);
        reportContents = new String(OpenCms.getPublishManager().getReportContents(publishJob));
        // check if the report states that the destination was published successfully
        if (!reportContents.matches("(?s)" + "(.*)" + file2 + "(.*)" + "o.k." + "(.*)")) {
            System.err.println(reportContents);
            fail("publish report contains errors");
        }
        if (!reportContents.matches("(?s)" + "(.*)" + folder + "(.*)" + "o.k." + "(.*)")) {
            System.err.println(reportContents);
            fail("publish report contains errors");
        }

        // check the published resources
        assertState(cms, folder, CmsResourceState.STATE_UNCHANGED);
        assertLock(cms, folder, CmsLockType.UNLOCKED);
        assertState(cms, file, CmsResourceState.STATE_UNCHANGED);
        assertLock(cms, file, CmsLockType.UNLOCKED);
        assertState(cms, file2, CmsResourceState.STATE_UNCHANGED);
        assertLock(cms, file2, CmsLockType.UNLOCKED);
    }

    /**
     * Test an issue with the publish queue when trying to publish a folder that has already a subresource that is being published.<p>
     * 
     * FIRST TRY:
     * - modify and lock a file, publish the file
     * - modify and lock a second file, publish the folder
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishQueueIssue1() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing a folder with a subresource in the publish queue, first try");

        // stop the publish engine to ensure that the file will not be published before we try to publish the folder
        OpenCms.getPublishManager().stopPublishing();

        // touch and publish a file in a folder
        String folder = "/folder1/subfolder11/subsubfolder111/";
        String file = folder + "image.gif";
        cms.lockResource(file);
        cms.setDateLastModified(file, System.currentTimeMillis(), false);
        OpenCms.getPublishManager().publishResource(cms, file);

        // check the publish queue
        assertEquals(1, OpenCms.getPublishManager().getPublishQueue().size());

        // touch a second file and publish the file
        String file2 = folder + "text.txt";
        cms.lockResource(file2);
        cms.setDateLastModified(file2, System.currentTimeMillis(), false);
        OpenCms.getPublishManager().publishResource(cms, folder);

        // check the publish queue
        assertEquals(2, OpenCms.getPublishManager().getPublishQueue().size());

        // start the publish engine
        OpenCms.getPublishManager().startPublishing();
        OpenCms.getPublishManager().waitWhileRunning();

        // check the publish queue
        assertEquals(0, OpenCms.getPublishManager().getPublishQueue().size());

        // check publish reports
        List history = OpenCms.getPublishManager().getPublishHistory();
        CmsPublishJobFinished publishJob = (CmsPublishJobFinished)history.get(history.size() - 2);
        String reportContents = new String(OpenCms.getPublishManager().getReportContents(publishJob));
        // check if the report states that the destination was published successfully
        if (!reportContents.matches("(?s)" + "(.*)" + file + "(.*)" + "o.k." + "(.*)")) {
            System.err.println(reportContents);
            fail("publish report contains errors");
        }
        publishJob = (CmsPublishJobFinished)history.get(history.size() - 1);
        reportContents = new String(OpenCms.getPublishManager().getReportContents(publishJob));
        // check if the report states that the destination was published successfully
        if (!reportContents.matches("(?s)" + "(.*)" + file2 + "(.*)" + "o.k." + "(.*)")) {
            System.err.println(reportContents);
            fail("publish report contains errors");
        }

        // check the published resources
        assertState(cms, folder, CmsResourceState.STATE_UNCHANGED);
        assertLock(cms, folder, CmsLockType.UNLOCKED);
        assertState(cms, file, CmsResourceState.STATE_UNCHANGED);
        assertLock(cms, file, CmsLockType.UNLOCKED);
        assertState(cms, file2, CmsResourceState.STATE_UNCHANGED);
        assertLock(cms, file2, CmsLockType.UNLOCKED);
    }

    /**
     * Test publishing changed files.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishChangedFiles() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publish changed files");

        String resource1 = "/folder2/image1_new.gif";
        String resource2 = "/folder2/image1_sibling1.gif";
        String resource3 = "/folder2/image1_sibling2.gif";
        String resource4 = "/folder2/image1_sibling3.gif";

        CmsProject onlineProject = cms.readProject("Online");
        CmsProject offlineProject = cms.readProject("Offline");

        CmsProperty prop1;
        CmsProperty prop2;
        CmsProperty prop3;
        CmsProperty prop4;

        // make changes to the resources 
        // do not need to make any changes to resource3 and resource4 as they are
        // siblings of resource2!

        cms.lockResource(resource1);
        cms.lockResource(resource2);

        cms.writePropertyObject(resource1, new CmsProperty("Title", resource1, null));
        cms.writePropertyObject(resource2, new CmsProperty("Title", resource2, null));
        cms.writePropertyObject(resource3, new CmsProperty("Title", resource3, null));
        cms.writePropertyObject(resource4, new CmsProperty("Title", resource4, null));

        // unlock all resources
        cms.unlockResource(resource1);
        cms.unlockResource(resource2);

        storeResources(cms, resource1);
        storeResources(cms, resource2);
        storeResources(cms, resource3);
        storeResources(cms, resource4);

        // publish a modified resource without siblings
        //
        OpenCms.getPublishManager().publishResource(cms, resource1);
        OpenCms.getPublishManager().waitWhileRunning();

        // the online file must the offline changes
        cms.getRequestContext().setCurrentProject(onlineProject);
        prop1 = cms.readPropertyObject(resource1, "Title", false);

        if (!prop1.getValue().equals((resource1))) {
            fail("Property not published for " + resource1);
        }
        assertFilter(cms, resource1, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);

        // check if the file in the offline project is unchancged now
        cms.getRequestContext().setCurrentProject(offlineProject);
        assertState(cms, resource1, CmsResource.STATE_UNCHANGED);

        // publish a modified resource with siblings, but keeping the siblings unpublished
        //
        OpenCms.getPublishManager().publishResource(cms, resource2);
        OpenCms.getPublishManager().waitWhileRunning();

        // the online file must the offline changes
        cms.getRequestContext().setCurrentProject(onlineProject);
        prop2 = cms.readPropertyObject(resource2, "Title", false);
        prop3 = cms.readPropertyObject(resource3, "Title", false);
        prop4 = cms.readPropertyObject(resource4, "Title", false);

        if (!prop2.getValue().equals((resource2))) {
            fail("Property not published for " + resource2);
        }
        if (prop3.getValue().equals((resource3))) {
            fail("Property  published for " + resource3);
        }
        if (prop4.getValue().equals((resource4))) {
            fail("Property  published for " + resource4);
        }

        assertFilter(cms, resource2, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);

        // check if the file in the offline project is unchancged now
        cms.getRequestContext().setCurrentProject(offlineProject);
        assertState(cms, resource2, CmsResource.STATE_UNCHANGED);
        assertState(cms, resource3, CmsResource.STATE_CHANGED);
        assertState(cms, resource4, CmsResource.STATE_CHANGED);

        // publish a modified resource with siblings, publish the siblings as well
        //
        OpenCms.getPublishManager().publishResource(
            cms,
            resource3,
            true,
            new CmsShellReport(cms.getRequestContext().getLocale()));
        OpenCms.getPublishManager().waitWhileRunning();

        // the online file must the offline changes
        cms.getRequestContext().setCurrentProject(onlineProject);
        prop3 = cms.readPropertyObject(resource3, "Title", false);
        prop4 = cms.readPropertyObject(resource4, "Title", false);

        if (!prop3.getValue().equals((resource3))) {
            fail("Property  not published for " + resource3);
        }
        if (!prop4.getValue().equals((resource4))) {
            fail("Property  not published for " + resource4);
        }

        assertFilter(cms, resource3, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);
        assertFilter(cms, resource4, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);

        // check if the file in the offline project is unchancged now
        cms.getRequestContext().setCurrentProject(offlineProject);
        assertState(cms, resource3, CmsResource.STATE_UNCHANGED);
        assertState(cms, resource4, CmsResource.STATE_UNCHANGED);
    }

    /**
     * Tests the behaviour during publishing depending on the content date.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishContentDate() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing the behaviour during publishing depending on the content date");

        String resName = "/newtext.txt";

        // test creating content
        long time = System.currentTimeMillis();
        synchronized (this) {
            wait(50);
        }
        CmsResource offlineResource = cms.createResource(resName, CmsResourceTypePlain.getStaticTypeId(), null, null);
        CmsFile offlineFile = cms.readFile(offlineResource);
        offlineFile.setContents("abc".getBytes());
        offlineFile = cms.writeFile(offlineFile);
        synchronized (this) {
            wait(50);
        }
        assertTrue(offlineResource.getDateContent() < System.currentTimeMillis());
        assertDateContentAfter(cms, resName, time);

        // test publishing new content
        OpenCms.getPublishManager().publishResource(cms, resName);
        OpenCms.getPublishManager().waitWhileRunning();

        CmsProject onlineProject = cms.readProject("Online");
        cms.getRequestContext().setCurrentProject(onlineProject);
        assertDateContentAfter(cms, resName, offlineResource.getDateContent());
        synchronized (this) {
            wait(50);
        }
        assertTrue(cms.readResource(offlineResource.getStructureId()).getDateContent() < System.currentTimeMillis());

        // test modifying the content
        CmsProject offlineProject = cms.readProject("Offline");
        cms.getRequestContext().setCurrentProject(offlineProject);

        cms.lockResource(resName);
        offlineFile = cms.readFile(offlineResource);
        time = System.currentTimeMillis();
        synchronized (this) {
            wait(50);
        }
        offlineFile.setContents("xyz".getBytes());
        offlineFile = cms.writeFile(offlineFile);
        offlineFile = cms.readFile(resName);
        assertDateContentAfter(cms, resName, time);

        // check the online project before publishing
        cms.getRequestContext().setCurrentProject(onlineProject);
        assertDateContentAfter(cms, resName, offlineResource.getDateContent());

        cms.getRequestContext().setCurrentProject(offlineProject);

        // now publish and check again
        OpenCms.getPublishManager().publishResource(cms, resName);
        OpenCms.getPublishManager().waitWhileRunning();

        cms.getRequestContext().setCurrentProject(onlineProject);
        assertDateContentAfter(cms, resName, offlineResource.getDateContent());

        // test modifying just a property/attribute
        cms.getRequestContext().setCurrentProject(offlineProject);
        cms.lockResource(resName);
        time = cms.readResource(resName).getDateContent();
        synchronized (this) {
            wait(50);
        }
        cms.writePropertyObject(resName, new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, "abc", "def"));
        cms.setDateExpired(resName, System.currentTimeMillis() + 100000, false);
        assertDateContent(cms, resName, time);

        // check the online project before publishing
        cms.getRequestContext().setCurrentProject(onlineProject);
        assertDateContentAfter(cms, resName, time);

        // now publish and check again
        cms.getRequestContext().setCurrentProject(offlineProject);
        time = cms.readResource(resName).getDateContent();
        synchronized (this) {
            wait(50);
        }
        OpenCms.getPublishManager().publishResource(cms, resName);
        OpenCms.getPublishManager().waitWhileRunning();

        cms.getRequestContext().setCurrentProject(onlineProject);
        assertDateContentAfter(cms, resName, time);

        // test creating a sibling
        String sibName = "/newtext_sib.txt";

        cms.getRequestContext().setCurrentProject(offlineProject);
        time = cms.readResource(resName).getDateContent();
        synchronized (this) {
            wait(50);
        }
        CmsResource siblingOffline = cms.createSibling(resName, sibName, null);
        assertDateContent(cms, sibName, time);
        assertDateContent(cms, resName, time);

        synchronized (this) {
            wait(50);
        }
        // now publish and check
        OpenCms.getPublishManager().publishResource(cms, sibName);
        OpenCms.getPublishManager().waitWhileRunning();

        cms.getRequestContext().setCurrentProject(onlineProject);
        assertDateContentAfter(cms, sibName, time);
        assertDateContentAfter(cms, resName, time);

        // test modifying the content of a sibling
        cms.getRequestContext().setCurrentProject(offlineProject);

        cms.lockResource(resName);
        offlineFile = cms.readFile(resName);
        time = System.currentTimeMillis();
        synchronized (this) {
            wait(50);
        }
        offlineFile.setContents("test".getBytes());
        offlineFile = cms.writeFile(offlineFile);
        offlineFile = cms.readFile(resName);
        synchronized (this) {
            wait(50);
        }

        assertTrue(offlineFile.getDateContent() < System.currentTimeMillis());
        assertDateContentAfter(cms, resName, time);
        assertDateContentAfter(cms, sibName, time);

        // check the online project before publishing
        cms.getRequestContext().setCurrentProject(onlineProject);
        assertDateContentAfter(cms, resName, offlineResource.getDateContent());
        siblingOffline = cms.readResource(sibName);
        assertDateContentAfter(cms, sibName, siblingOffline.getDateContent());

        // now publish and check again
        cms.getRequestContext().setCurrentProject(offlineProject);
        synchronized (this) {
            wait(50);
        }
        OpenCms.getPublishManager().publishResource(cms, resName);
        OpenCms.getPublishManager().waitWhileRunning();

        cms.getRequestContext().setCurrentProject(onlineProject);
        assertDateContentAfter(cms, resName, offlineResource.getDateContent());
        assertDateContentAfter(cms, sibName, offlineResource.getDateContent());
        assertDateContent(cms, sibName, cms.readResource(resName).getDateContent());
        long onlineTime = cms.readResource(resName).getDateContent();

        // test moving
        String movedName = "/newtext_mov.txt";

        cms.getRequestContext().setCurrentProject(offlineProject);
        cms.lockResource(resName);
        time = cms.readResource(resName).getDateContent();
        synchronized (this) {
            wait(50);
        }
        // this will NOT write the content
        cms.moveResource(resName, movedName);
        offlineFile = cms.readFile(movedName);

        assertDateContent(cms, movedName, time);
        assertDateContent(cms, sibName, time);

        // check the online project before publishing
        cms.getRequestContext().setCurrentProject(onlineProject);
        assertDateContent(cms, resName, onlineTime);
        assertDateContent(cms, sibName, onlineTime);

        // now publish and check again
        cms.getRequestContext().setCurrentProject(offlineProject);
        synchronized (this) {
            wait(50);
        }
        OpenCms.getPublishManager().publishResource(cms, movedName);
        OpenCms.getPublishManager().waitWhileRunning();

        cms.getRequestContext().setCurrentProject(onlineProject);
        assertDateContent(cms, movedName, time);
        assertDateContent(cms, sibName, time);
    }

    /**
     * Tests the publishing of a fresh created sibling.<p>
     * 
     * take a modified resource:
     * - create a sibling of it 
     * - publish only the sibling 
     * - compare online/offline versions of the sibling
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishCopySibling() throws Throwable {

        CmsObject cms = getCmsObject();
        String source = "/folder1/page1.html";
        echo("testing the publishing of a fresh created sibling");

        long time = System.currentTimeMillis();
        cms.lockResource(source);
        cms.setDateLastModified(source, time, false);
        synchronized (this) {
            wait(100);
        }

        String destination = "/folder1/sibling2.html";
        cms.copyResource(source, destination, CmsResource.COPY_AS_SIBLING);

        OpenCms.getPublishManager().publishResource(cms, destination);
        OpenCms.getPublishManager().waitWhileRunning();

        long newTime = cms.readResource(destination).getDateLastModified();

        cms.getRequestContext().setCurrentProject(cms.readProject(CmsProject.ONLINE_PROJECT_ID));
        assertEquals(
            "File is unchanged and online date last modified is not the same as offline",
            newTime,
            cms.readResource(destination).getDateLastModified());
        assertState(cms, destination, CmsResource.STATE_UNCHANGED);
    }

    /**
     * Test publishing deleted files.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishDeletedFiles() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publish deleted files");

        String resource1 = "/folder2/image1_new.gif";
        String resource2 = "/folder2/image1_sibling1.gif";
        String resource3 = "/folder2/image1_sibling2.gif";
        String resource4 = "/folder2/image1_sibling3.gif";

        CmsProject onlineProject = cms.readProject("Online");
        CmsProject offlineProject = cms.readProject("Offline");

        // publish a deleted resource without siblings
        //

        // delete the resources 
        cms.lockResource(resource1);
        cms.deleteResource(resource1, CmsResource.DELETE_PRESERVE_SIBLINGS);
        cms.unlockResource(resource1);

        OpenCms.getPublishManager().publishResource(cms, resource1);
        OpenCms.getPublishManager().waitWhileRunning();

        // the online file must be deleted
        cms.getRequestContext().setCurrentProject(onlineProject);
        try {
            cms.readResource(resource1);
            fail("Resource " + resource1 + " was not deleted online");
        } catch (CmsVfsResourceNotFoundException e) {
            // ok
        }

        cms.getRequestContext().setCurrentProject(offlineProject);

        // publish a deleted resource with siblings, 
        // delete the siblings also, but publish only the resource itself

        // delete the resources 
        cms.lockResource(resource2);
        cms.deleteResource(resource2, CmsResource.DELETE_REMOVE_SIBLINGS);
        cms.unlockResource(resource2);

        // this test makes only sense when siblings are published
        OpenCms.getPublishManager().publishResource(
            cms,
            resource2,
            false,
            new CmsShellReport(cms.getRequestContext().getLocale()));
        OpenCms.getPublishManager().waitWhileRunning();

        // the files must be unlocked
        List lockedResources = cms.getLockedResources("/folder2", CmsLockFilter.FILTER_ALL);
        assertTrue(lockedResources.isEmpty());

        // the online file must be deleted
        cms.getRequestContext().setCurrentProject(onlineProject);
        try {
            cms.readResource(resource2);
            fail("Resource " + resource2 + " was not deleted online");
        } catch (CmsVfsResourceNotFoundException e) {
            // ok
        }
        // the other siblings must still be there
        try {
            cms.readResource(resource3);
        } catch (CmsException e) {
            fail("Resource " + resource3 + " error:" + e);
        }
        try {
            cms.readResource(resource4);
        } catch (CmsException e) {
            fail("Resource " + resource4 + " error:" + e);
        }

        cms.getRequestContext().setCurrentProject(offlineProject);
        // in the offline project, the siblings must be still marked as deleted
        assertState(cms, resource3, CmsResource.STATE_DELETED);
        assertState(cms, resource4, CmsResource.STATE_DELETED);

        // publish a deleted resource with siblings, delete the siblings
        //
        OpenCms.getPublishManager().publishResource(
            cms,
            resource3,
            true,
            new CmsShellReport(cms.getRequestContext().getLocale()));
        OpenCms.getPublishManager().waitWhileRunning();

        // the online files must be deleted
        cms.getRequestContext().setCurrentProject(onlineProject);
        try {
            cms.readResource(resource3);
            fail("Resource " + resource3 + " was not deleted online");
        } catch (CmsVfsResourceNotFoundException e) {
            // ok
        }
        try {
            cms.readResource(resource4);
            fail("Resource " + resource4 + " was not deleted online");
        } catch (CmsVfsResourceNotFoundException e) {
            // ok
        }

        cms.getRequestContext().setCurrentProject(offlineProject);

    }

    /**
     * Tests the publishing of deleted siblings.<p>
     * 
     * take 2 siblings (s1 and s2):
     *  - delete one 
     *  - publish the deleted one 
     *  - the remaining sibling should be readable in online project
     *  - the deleted sibling should be eligible for restoration
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishDeletedSiblings() throws Throwable {

        CmsObject cms = getCmsObject();

        String source = "/testdeletesib1.html";
        String sibling = "/testdeletesib2.html";

        echo("testing publishing of deleted siblings");

        // create the siblings
        CmsResource src = cms.createResource(source, CmsResourceTypePlain.getStaticTypeId());
        CmsFile file = cms.readFile(src);
        file.setContents("test text".getBytes());
        cms.writeFile(file);

        cms.createSibling(source, sibling, null);

        // publish both
        OpenCms.getPublishManager().publishResource(
            cms,
            source,
            true,
            new CmsShellReport(cms.getRequestContext().getLocale()));
        OpenCms.getPublishManager().waitWhileRunning();

        CmsProject online = cms.readProject(CmsProject.ONLINE_PROJECT_ID);
        CmsProject offline = cms.getRequestContext().currentProject();

        // store the online versions
        cms.getRequestContext().setCurrentProject(online);
        storeResources(cms, source);
        storeResources(cms, sibling);
        cms.getRequestContext().setCurrentProject(offline);

        // delete one sibling
        cms.lockResource(sibling);
        cms.deleteResource(sibling, CmsResource.DELETE_PRESERVE_SIBLINGS);
        cms.unlockResource(sibling);

        // check the online versions
        cms.getRequestContext().setCurrentProject(online);
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, sibling, OpenCmsTestResourceFilter.FILTER_EQUAL);
        cms.getRequestContext().setCurrentProject(offline);

        // publish just the deleted sibling
        OpenCms.getPublishManager().publishResource(
            cms,
            sibling,
            false,
            new CmsShellReport(cms.getRequestContext().getLocale()));
        OpenCms.getPublishManager().waitWhileRunning();

        cms.getRequestContext().setCurrentProject(online);
        assertFalse(cms.existsResource(sibling, CmsResourceFilter.ALL));
        assertTrue(cms.existsResource(source, CmsResourceFilter.ALL));
        assertEquals(new String("test text".getBytes()), new String(cms.readFile(source).getContents()));
        cms.getRequestContext().setCurrentProject(offline);

        List list = cms.readDeletedResources("/", false);
        CmsHistoryFile histFile = (CmsHistoryFile)list.get(0);
        assertEquals(cms.getRequestContext().addSiteRoot(sibling), histFile.getRootPath());
        assertEquals(2, histFile.getVersion());
        file = cms.readFile(histFile);
        assertEquals(new String("test text".getBytes()), new String(file.getContents()));

        // now delete the 2nd sibling
        cms.lockResource(source);
        cms.deleteResource(source, CmsResource.DELETE_PRESERVE_SIBLINGS);
        cms.unlockResource(source);

        // publish the deleted sibling
        OpenCms.getPublishManager().publishResource(cms, source);
        OpenCms.getPublishManager().waitWhileRunning();

        cms.getRequestContext().setCurrentProject(online);
        assertFalse(cms.existsResource(sibling, CmsResourceFilter.ALL));
        assertFalse(cms.existsResource(source, CmsResourceFilter.ALL));
        cms.getRequestContext().setCurrentProject(offline);

        list = cms.readDeletedResources("/", false);
        assertEquals(2, list.size());
        histFile = (CmsHistoryFile)list.get(0);
        assertEquals(cms.getRequestContext().addSiteRoot(source), histFile.getRootPath());
        assertEquals(2, histFile.getVersion());
        file = cms.readFile(histFile);
        assertEquals(new String("test text".getBytes()), new String(file.getContents()));
        CmsHistoryFile histFile2 = (CmsHistoryFile)list.get(1);
        assertEquals(cms.getRequestContext().addSiteRoot(sibling), histFile2.getRootPath());
        assertEquals(2, histFile2.getVersion());
        file = cms.readFile(histFile2);
        assertEquals(new String("test text".getBytes()), new String(file.getContents()));
    }

    /**
     * Tests the publishing of deleted siblings, 2nd issue.<p>
     * 
     * take 2 siblings (s1 and s2):
     *  - delete all
     *  - publish all
     *  - non of them should be readable
     *  - both siblings should be eligible for restoration
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishDeletedSiblings2() throws Throwable {

        CmsObject cms = getCmsObject();

        String folder = "/testdelete";
        String source = "/testdelete/testdeletesib2-1.html";
        String sibling = "/testdelete/testdeletesib2-2.html";

        echo("testing publishing of deleted siblings, 2nd issue");

        cms.createResource(folder, CmsResourceTypeFolder.RESOURCE_TYPE_ID);
        // create the siblings
        CmsResource src = cms.createResource(source, CmsResourceTypePlain.getStaticTypeId());
        CmsFile file = cms.readFile(src);
        file.setContents("test text".getBytes());
        cms.writeFile(file);

        cms.createSibling(source, sibling, null);

        // publish all
        OpenCms.getPublishManager().publishResource(cms, folder);
        OpenCms.getPublishManager().waitWhileRunning();

        CmsProject online = cms.readProject(CmsProject.ONLINE_PROJECT_ID);
        CmsProject offline = cms.getRequestContext().currentProject();

        // store the online versions
        cms.getRequestContext().setCurrentProject(online);
        storeResources(cms, source);
        storeResources(cms, sibling);
        cms.getRequestContext().setCurrentProject(offline);

        // delete both siblings
        cms.lockResource(sibling);
        cms.deleteResource(sibling, CmsResource.DELETE_REMOVE_SIBLINGS);
        cms.unlockResource(sibling);

        // check the online versions
        cms.getRequestContext().setCurrentProject(online);
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, sibling, OpenCmsTestResourceFilter.FILTER_EQUAL);
        cms.getRequestContext().setCurrentProject(offline);

        // publish both deleted siblings
        OpenCms.getPublishManager().publishResource(
            cms,
            sibling,
            true,
            new CmsShellReport(cms.getRequestContext().getLocale()));
        OpenCms.getPublishManager().waitWhileRunning();

        cms.getRequestContext().setCurrentProject(online);
        assertFalse(cms.existsResource(sibling, CmsResourceFilter.ALL));
        assertFalse(cms.existsResource(source, CmsResourceFilter.ALL));
        cms.getRequestContext().setCurrentProject(offline);

        List list = cms.readDeletedResources(folder, false);
        assertEquals(2, list.size());
        CmsHistoryFile histFile = (CmsHistoryFile)list.get(0);
        assertEquals(cms.getRequestContext().addSiteRoot(source), histFile.getRootPath());
        assertEquals(2, histFile.getVersion());
        file = cms.readFile(histFile);
        assertEquals(new String("test text".getBytes()), new String(file.getContents()));
        CmsHistoryFile histFile2 = (CmsHistoryFile)list.get(1);
        assertEquals(cms.getRequestContext().addSiteRoot(sibling), histFile2.getRootPath());
        assertEquals(2, histFile2.getVersion());
        file = cms.readFile(histFile2);
        assertEquals(new String("test text".getBytes()), new String(file.getContents()));
    }

    /**
     * Test publishing a folder with locked files.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishFolderWithLockedFiles() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publish a folder with locked files");

        String folder = "/folder2/subfolder21/";
        String resource = "image1.gif";

        // touch the folder
        cms.lockResource(folder);
        cms.setDateLastModified(folder, System.currentTimeMillis(), false);
        cms.unlockResource(folder);

        // lock a resource in the folder
        cms.lockResource(folder + resource);
        cms.setDateLastModified(folder + resource, System.currentTimeMillis(), false);

        // publish the folder without subresources
        CmsPublishList publishList = OpenCms.getPublishManager().getPublishList(
            cms,
            Collections.singletonList(cms.readResource(folder)),
            false,
            false);
        OpenCms.getPublishManager().publishProject(
            cms,
            new CmsShellReport(cms.getRequestContext().getLocale()),
            publishList);
        OpenCms.getPublishManager().waitWhileRunning();

        // ensure the folder has been published
        assertState(cms, folder, CmsResource.STATE_UNCHANGED);
        // ensure the folder is unlocked
        assertLock(cms, folder, CmsLockType.UNLOCKED);
        // ensure that the changed resource is still changed in the offline project
        assertState(cms, folder + resource, CmsResource.STATE_CHANGED);
        // ensure the resource is still locked
        assertLock(cms, folder + resource);
    }

    /**
     * Test publishing changed files.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishLockedFiles() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publish locked files");

        // switch user
        cms.loginUser("test1", "test1");
        cms.getRequestContext().setCurrentProject(cms.readProject("Offline"));
        cms.getRequestContext().setSiteRoot("/sites/default/");

        String source = "/folder2/subfolder21/image1.gif";
        String resource1 = "/folder2/image1_new.gif";
        String resource2 = "/folder2/image1_sibling1.gif";

        // make changes to the resources 
        // do not need to make any changes to resource3 and resource4 as they are
        // siblings of resource2!

        cms.lockResource(source);
        cms.lockResource(resource1);
        cms.lockResource(resource2);

        cms.writePropertyObject(source, new CmsProperty("Title", source + " modified", null));
        cms.writePropertyObject(resource1, new CmsProperty("Title", resource1 + " modified", null));
        cms.writePropertyObject(resource2, new CmsProperty("Title", resource2 + " modified", null));

        // switch user, so the locked resources can not be published until they are explicit unlocked
        cms.loginUser("Admin", "admin");
        cms.getRequestContext().setCurrentProject(cms.readProject("Offline"));
        cms.getRequestContext().setSiteRoot("/sites/default/");

        storeResources(cms, source);
        storeResources(cms, resource1);
        storeResources(cms, resource2);

        // publish a modified resource without siblings
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();

        // ensure that all changed resources are still changed in the offline project
        assertState(cms, source, CmsResource.STATE_CHANGED);
        assertState(cms, resource1, CmsResource.STATE_CHANGED);
        assertState(cms, resource2, CmsResource.STATE_CHANGED);

        // lock the resources, so the next test can run
        cms.changeLock(source);
        cms.changeLock(resource1);
        cms.changeLock(resource2);

        // ensure that all changed resources are NOT published
        cms.getRequestContext().setCurrentProject(cms.readProject("Online"));
        CmsProperty prop0 = cms.readPropertyObject(source, "Title", false);
        CmsProperty prop1 = cms.readPropertyObject(resource1, "Title", false);
        CmsProperty prop2 = cms.readPropertyObject(resource2, "Title", false);

        if (prop0.getValue().equals((source + " modified"))) {
            fail("Property published for " + source);
        }
        if (prop1.getValue().equals((resource1 + " modified"))) {
            fail("Property published for " + resource1);
        }
        if (prop2.getValue().equals((resource2 + " modified"))) {
            fail("Property published for " + resource2);
        }
    }

    /**
     * Test publishing moved files.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishMovedFiles() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publish moved files");

        String resource1 = "/folder1/subfolder12/subsubfolder121/image1.gif";
        String target1 = "/folder1/subfolder12/subsubfolder121/image1_new.gif";

        CmsProject onlineProject = cms.readProject("Online");
        CmsProject offlineProject = cms.readProject("Offline");

        // move the resource 
        cms.lockResource(resource1);
        cms.moveResource(resource1, target1);
        cms.unlockResource(target1);

        storeResources(cms, target1);

        // publish it
        CmsPublishList publishList = OpenCms.getPublishManager().getPublishList(cms, cms.readResource(target1), false);
        OpenCms.getPublishManager().publishProject(
            cms,
            new CmsShellReport(cms.getRequestContext().getLocale()),
            publishList);
        OpenCms.getPublishManager().waitWhileRunning();

        // check publish resource state
        List pubResources = cms.readPublishedResources(publishList.getPublishHistoryId());
        assertEquals(2, pubResources.size());
        CmsPublishedResource pubRes = (CmsPublishedResource)pubResources.get(0);
        assertEquals(cms.getRequestContext().addSiteRoot(resource1), pubRes.getRootPath());
        assertEquals(CmsPublishedResource.STATE_MOVED_SOURCE, pubRes.getMovedState());
        pubRes = (CmsPublishedResource)pubResources.get(1);
        assertEquals(cms.getRequestContext().addSiteRoot(target1), pubRes.getRootPath());
        assertEquals(CmsPublishedResource.STATE_MOVED_DESTINATION, pubRes.getMovedState());

        // the online original file must be deleted
        cms.getRequestContext().setCurrentProject(onlineProject);
        try {
            cms.readResource(resource1, CmsResourceFilter.ALL);
            fail("Resource " + resource1 + " was not deleted online");
        } catch (CmsVfsResourceNotFoundException e) {
            // ok
        } catch (CmsException e) {
            fail("Resource " + resource1 + " error:" + e);
        }
        try {
            cms.readResource(target1);
        } catch (CmsVfsResourceNotFoundException e) {
            fail("Resource " + target1 + " is not online");
        } catch (CmsException e) {
            fail("Resource " + target1 + " error:" + e);
        }
        assertFilter(cms, target1, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);

        cms.getRequestContext().setCurrentProject(offlineProject);
        try {
            cms.readResource(resource1, CmsResourceFilter.ALL);
            fail("Resource " + resource1 + " was not deleted offline");
        } catch (CmsVfsResourceNotFoundException e) {
            // ok
        } catch (CmsException e) {
            fail("Resource " + resource1 + " error:" + e);
        }
        try {
            cms.readResource(target1);
        } catch (CmsVfsResourceNotFoundException e) {
            fail("Resource " + target1 + " is not offline");
        } catch (CmsException e) {
            fail("Resource " + target1 + " error:" + e);
        }
        assertState(cms, target1, CmsResource.STATE_UNCHANGED);

    }

    /**
     * Test publishing new files.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishNewFiles() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publish new files");

        String source = "/folder2/subfolder21/image1.gif";
        String destination1 = "/folder2/image1_new.gif";
        String destination2 = "/folder2/image1_sibling1.gif";
        String destination3 = "/folder2/image1_sibling2.gif";
        String destination4 = "/folder2/image1_sibling3.gif";

        CmsProject onlineProject = cms.readProject("Online");
        CmsProject offlineProject = cms.readProject("Offline");

        // make four copies of a file to be published later
        cms.copyResource(source, destination1, CmsResource.COPY_AS_NEW);
        cms.copyResource(source, destination2, CmsResource.COPY_AS_SIBLING);
        cms.copyResource(source, destination3, CmsResource.COPY_AS_SIBLING);
        cms.copyResource(source, destination4, CmsResource.COPY_AS_SIBLING);

        storeResources(cms, destination1);
        storeResources(cms, destination2);
        storeResources(cms, destination3);
        storeResources(cms, destination4);

        // unlock all new resources
        // do not neet do unlock destination3 as it is a sibling of destination2     
        cms.unlockResource(destination1);
        cms.unlockResource(destination2);

        // publish a new resource
        //
        OpenCms.getPublishManager().publishResource(cms, destination1);
        OpenCms.getPublishManager().waitWhileRunning();

        // the file must be now available in the online project
        cms.getRequestContext().setCurrentProject(onlineProject);
        try {
            cms.readResource(destination1);
        } catch (CmsException e) {
            fail("Resource " + destination1 + " not found in online project:" + e);
        }
        assertFilter(cms, destination1, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);

        // check if the file in the offline project is unchanged now
        cms.getRequestContext().setCurrentProject(offlineProject);
        assertState(cms, destination1, CmsResource.STATE_UNCHANGED);

        // publish a sibling without publishing other siblings
        //
        OpenCms.getPublishManager().publishResource(cms, destination2);
        OpenCms.getPublishManager().waitWhileRunning();

        // the file must be now available in the online project
        cms.getRequestContext().setCurrentProject(onlineProject);
        try {
            cms.readResource(destination2);
        } catch (CmsException e) {
            fail("Resource " + destination2 + " not found in online project:" + e);
        }
        // the other siblings must not be available in the online project yet
        try {
            cms.readResource(destination3);
            fail("Resource " + destination3 + " should not available online yet");
        } catch (CmsVfsResourceNotFoundException e) {
            // ok
        } catch (CmsException e) {
            fail("Resource " + destination3 + " error:" + e);
        }
        try {
            cms.readResource(destination4);
            fail("Resource " + destination4 + " should not available online yet");
        } catch (CmsVfsResourceNotFoundException e) {
            // ok
        } catch (CmsException e) {
            fail("Resource " + destination4 + " error:" + e);
        }

        assertFilter(cms, destination2, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);

        // check if the file in the offline project is unchanged now
        cms.getRequestContext().setCurrentProject(offlineProject);
        assertState(cms, destination2, CmsResource.STATE_UNCHANGED);
        // the other siblings in the offline project must still be shown as new
        assertState(cms, destination3, CmsResource.STATE_NEW);
        assertState(cms, destination4, CmsResource.STATE_NEW);

        // publish a sibling and all other siblings of it
        //
        OpenCms.getPublishManager().publishResource(
            cms,
            destination3,
            true,
            new CmsShellReport(cms.getRequestContext().getLocale()));
        OpenCms.getPublishManager().waitWhileRunning();
        // the file and its siblings must be now available in the online project
        cms.getRequestContext().setCurrentProject(onlineProject);
        try {
            cms.readResource(destination3);
        } catch (CmsException e) {
            fail("Resource " + destination3 + " not found in online project:" + e);
        }
        try {
            cms.readResource(destination4);
        } catch (CmsException e) {
            fail("Resource " + destination4 + " not found in online project:" + e);
        }
        assertFilter(cms, destination3, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);
        assertFilter(cms, destination4, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);

        // check if the file in the offline project is unchanged now
        cms.getRequestContext().setCurrentProject(offlineProject);
        assertState(cms, destination3, CmsResource.STATE_UNCHANGED);
        assertState(cms, destination4, CmsResource.STATE_UNCHANGED);
    }

    /**
     * Test publishing files within a new unpublished folder.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishNewFilesInNewFolder() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing new files in new folder");

        String source = "/folder1/image1.gif";
        String newFolder = "/new_folder/";
        String newFile = newFolder + "new_file";
        String newSibling = newFolder + "new_sibling";

        cms.createResource(newFolder, CmsResourceTypeFolder.getStaticTypeId());
        cms.unlockResource(newFolder);
        storeResources(cms, newFolder);

        // change to test project
        CmsProject project = getTestProject(cms);
        cms.getRequestContext().setCurrentProject(project);

        cms.copyResource(source, newFile, CmsResource.COPY_AS_NEW);
        cms.unlockResource(newFile);

        cms.copyResource(source, newSibling, CmsResource.COPY_AS_SIBLING);
        cms.unlockResource(newSibling);

        // direct publish of the new file will not publish the new file
        echo("Publishing the resource directly");

        storeResources(cms, newFile);

        boolean error = true;
        try {
            // this will generate an error in the log, ensure the test still continues
            OpenCmsTestLogAppender.setBreakOnError(false);
            OpenCms.getPublishManager().publishResource(cms, newFile);
            OpenCms.getPublishManager().waitWhileRunning();
        } catch (CmsMultiException e) {
            CmsVfsException ex = (CmsVfsException)e.getExceptions().get(0);
            if (ex.getMessageContainer().getKey() == org.opencms.db.Messages.ERR_DIRECT_PUBLISH_PARENT_NEW_2) {
                error = false;
            }
        }
        // reset log to stop test on error
        OpenCmsTestLogAppender.setBreakOnError(true);

        if (error) {
            fail("A resource in a new folder could be published without generating an error!");
        }

        assertFilter(cms, newFile, OpenCmsTestResourceFilter.FILTER_EQUAL);

        // direct publish of another sibling will not publish the new sibling
        echo("Publishing another sibling");
        cms.lockResource(source);
        cms.setDateLastModified(source, System.currentTimeMillis(), false);
        cms.unlockResource(source);

        storeResources(cms, newSibling);
        OpenCms.getPublishManager().publishResource(
            cms,
            source,
            true,
            new CmsShellReport(cms.getRequestContext().getLocale()));
        OpenCms.getPublishManager().waitWhileRunning();
        assertFilter(cms, newSibling, OpenCmsTestResourceFilter.FILTER_EQUAL);

        // publishing the test project will not publish the new file or the new sibling
        echo("Publishing the test project");
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();

        assertFilter(cms, newFile, OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, newSibling, OpenCmsTestResourceFilter.FILTER_EQUAL);

        // publishing the offline project will publish the folder but not the resources
        echo("Publishing the offline project");

        cms.getRequestContext().setCurrentProject(cms.readProject("Offline"));
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();

        assertFilter(cms, newFolder, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);
        assertState(cms, newFolder, CmsResource.STATE_UNCHANGED);

        assertFilter(cms, newFile, OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, newSibling, OpenCmsTestResourceFilter.FILTER_EQUAL);

        // publishing the test proejct again will now publish the resources
        echo("Publishing the test project again");
        cms.getRequestContext().setCurrentProject(project);
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();

        assertFilter(cms, newFile, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);
        assertState(cms, newFile, CmsResource.STATE_UNCHANGED);

        assertFilter(cms, newSibling, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);
        assertState(cms, newSibling, CmsResource.STATE_UNCHANGED);

    }

    /**
     * Test publishing a project with an iteration.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishProjectGalore() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing a project with an iteration");

        // publish project with several resources
        String resourcename = "/";
        cms.lockResource(resourcename);
        cms.setDateLastModified(resourcename, System.currentTimeMillis(), true);

        for (int i = 0; i < 10; i++) { // size of publish queue
            OpenCms.getPublishManager().publishProject(cms);
        }
        OpenCms.getPublishManager().waitWhileRunning();

        List pubHistory = OpenCms.getPublishManager().getPublishHistory();
        assertEquals(10, pubHistory.size());
        CmsPublishJobFinished pubJob = (CmsPublishJobFinished)pubHistory.get(0);
        assertEquals(13 + 51, pubJob.getSize()); // folders + files
        for (int i = 1; i < 10; i++) {
            pubJob = (CmsPublishJobFinished)pubHistory.get(i);
            assertEquals("pubJob: " + i, 0, pubJob.getSize());
        }
    }

    /**
     * Tests publishing resources within a distinct project.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishProjectLastmodified() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing a distinct project");

        String path = "/folder1";
        String res1 = path + "/page1.html";
        String res2 = path + "/page2.html";

        long timestamp = System.currentTimeMillis();

        cms.getRequestContext().setCurrentProject(cms.readProject("Online"));
        storeResources(cms, res1);

        // change first resource in the offline project
        cms.getRequestContext().setCurrentProject(cms.readProject("Offline"));
        cms.lockResource(res1);
        cms.setDateLastModified(res1, timestamp, false);

        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());

        // create a new project
        CmsProject project = getTestProject(cms);
        cms.getRequestContext().setCurrentProject(project);
        cms.copyResourceToProject(path);

        // and change another resource in this project
        cms.lockResource(res2);
        cms.setDateLastModified(res2, timestamp, false);
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        storeResources(cms, res2);

        // when the project is published, only the second resource will be published
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();

        cms.getRequestContext().setCurrentProject(cms.readProject("Online"));
        assertFilter(cms, res1, OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, res2, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);
        assertDateLastModified(cms, res2, timestamp);

        echo("Testing publishing in different projects");

        // change back to the test project, create a new resource
        String res3 = path + "/testPublishProjectLastmodified.txt";
        cms.getRequestContext().setCurrentProject(project);
        cms.createResource(res3, CmsResourceTypePlain.getStaticTypeId());

        // change to offline project, copy resource as sibling
        // this will also change the project in which the source was lastmodified (now "Offline")
        String res4 = "/testPublishProjectLastmodified.txt";
        cms.getRequestContext().setCurrentProject(cms.readProject("Offline"));
        cms.copyResource(res3, res4, CmsResource.COPY_AS_SIBLING);

        // check the current state of the resources
        assertState(cms, res3, CmsResource.STATE_NEW);
        assertState(cms, res4, CmsResource.STATE_NEW);

        // publish the test project
        cms.getRequestContext().setCurrentProject(project);
        cms.unlockProject(project.getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();

        // the resource inside the offline project must not be published (not in test project)
        assertState(cms, res3, CmsResource.STATE_NEW);

        // the sibling outside the offline project must not be published (not in test project)
        assertState(cms, res4, CmsResource.STATE_NEW);

        // publish the root folder of the test project within the test project
        OpenCms.getPublishManager().publishResource(
            cms,
            path,
            true,
            new CmsShellReport(cms.getRequestContext().getLocale()));
        OpenCms.getPublishManager().waitWhileRunning();

        // all resources inside the folder must be published, even if not in test project
        assertState(cms, res3, CmsResource.STATE_NEW);

        // as well as all siblings, even if not in test project
        assertState(cms, res4, CmsResource.STATE_NEW);
    }

    /**
     * Tests publishing a folder containing resources modified within a distinct project.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishProjectLastmodifiedFolder() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing a folder containing resources modified within a distinct project");

        String path = "/folder1";
        String res1 = path + "/index.html";

        // change first resource in the offline project
        cms.getRequestContext().setCurrentProject(cms.readProject("Offline"));
        cms.lockResource(res1);
        cms.setDateLastModified(res1, System.currentTimeMillis(), false);

        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        storeResources(cms, res1);

        // create a new project
        CmsProject project = getTestProject(cms);
        cms.getRequestContext().setCurrentProject(project);
        cms.copyResourceToProject(path);

        // check the publish list
        // the resource modified in the Offline project should not be there
        CmsPublishList pubList = OpenCms.getPublishManager().getPublishList(cms, cms.readResource(path), false);
        assertTrue(pubList.isPublishSubResources());
        assertTrue(pubList.getAllResources().isEmpty());
    }

    /**
     * Test publishing of related files.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishRelatedFiles() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing of related files");

        String resName = "index.html";

        // touch the file to publish        
        cms.lockResource(resName);
        cms.setDateLastModified(resName, System.currentTimeMillis(), false);
        CmsResource resource = cms.readResource(resName, CmsResourceFilter.DEFAULT);

        // get the publish list
        CmsPublishList pubList = OpenCms.getPublishManager().getPublishList(cms, resource, false);
        // just check the publish list
        assertTrue(pubList.getDeletedFolderList().isEmpty());
        assertTrue(pubList.getFolderList().isEmpty());
        assertEquals(1, pubList.getFileList().size());
        assertTrue(pubList.getFileList().contains(resource));

        // get the list of related resources, which should be still empty, since the related resource has unchanged state
        CmsPublishList relatedList = OpenCms.getPublishManager().getRelatedResourcesToPublish(cms, pubList);
        // check the publish list has not been touched
        assertTrue(pubList.getDeletedFolderList().isEmpty());
        assertTrue(pubList.getFolderList().isEmpty());
        assertEquals(1, pubList.getFileList().size());
        assertTrue(pubList.getFileList().contains(resource));
        // check the related publish list
        assertTrue(relatedList.getAllResources().isEmpty());

        String relResName = "folder1/image2.gif";

        // touch the related resource
        cms.lockResource(relResName);
        cms.setDateLastModified(relResName, System.currentTimeMillis(), false);
        CmsResource relatedRes = cms.readResource(relResName, CmsResourceFilter.DEFAULT);

        // get the list of related resources again
        relatedList = OpenCms.getPublishManager().getRelatedResourcesToPublish(cms, pubList);
        // check the publish list has not been touched
        assertTrue(pubList.getDeletedFolderList().isEmpty());
        assertTrue(pubList.getFolderList().isEmpty());
        assertEquals(1, pubList.getFileList().size());
        assertTrue(pubList.getFileList().contains(resource));
        // check the related publish list
        assertTrue(relatedList.getDeletedFolderList().isEmpty());
        assertTrue(relatedList.getFolderList().isEmpty());
        assertEquals(1, relatedList.getFileList().size());
        assertTrue(relatedList.getFileList().contains(relatedRes));

        CmsPublishList mergedList = OpenCms.getPublishManager().mergePublishLists(cms, pubList, relatedList);
        // check the publish list has not been touched
        assertTrue(pubList.getDeletedFolderList().isEmpty());
        assertTrue(pubList.getFolderList().isEmpty());
        assertEquals(1, pubList.getFileList().size());
        assertTrue(pubList.getFileList().contains(resource));
        // check the related publish list has not been touched
        assertTrue(relatedList.getDeletedFolderList().isEmpty());
        assertTrue(relatedList.getFolderList().isEmpty());
        assertEquals(1, relatedList.getFileList().size());
        assertTrue(relatedList.getFileList().contains(relatedRes));
        // check the merged publish list
        assertTrue(mergedList.getDeletedFolderList().isEmpty());
        assertTrue(mergedList.getFolderList().isEmpty());
        assertEquals(2, mergedList.getFileList().size());
        assertTrue(mergedList.getFileList().contains(relatedRes));
        assertTrue(mergedList.getFileList().contains(resource));
    }

    /**
     * Test publishing of related files taken a whole folder structure.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishRelatedFilesInFolder() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing of related files taken a whole folder structure");

        String resName = "/folder1/";

        // touch the file to publish        
        cms.lockResource(resName);
        cms.setDateLastModified(resName, System.currentTimeMillis(), true);
        CmsResource resource = cms.readResource(resName, CmsResourceFilter.DEFAULT);

        // get the publish list
        CmsPublishList pubList = OpenCms.getPublishManager().getPublishList(cms, resource, false);
        // just check the publish list
        assertTrue(pubList.getDeletedFolderList().isEmpty());
        assertFalse(pubList.getFolderList().isEmpty());
        assertFalse(pubList.getFileList().isEmpty());

        // get the list of related resources, which should be still empty, since the related resource has unchanged state
        CmsPublishList relatedList = OpenCms.getPublishManager().getRelatedResourcesToPublish(cms, pubList);
        // check the publish list has not been touched
        assertTrue(pubList.getDeletedFolderList().isEmpty());
        assertFalse(pubList.getFolderList().isEmpty());
        assertFalse(pubList.getFileList().isEmpty());
        // check the related publish list
        assertTrue(relatedList.getAllResources().isEmpty());

        CmsPublishList mergedList = OpenCms.getPublishManager().mergePublishLists(cms, pubList, relatedList);
        // check the publish list has not been touched
        assertTrue(pubList.getDeletedFolderList().isEmpty());
        assertFalse(pubList.getFolderList().isEmpty());
        assertFalse(pubList.getFileList().isEmpty());
        // check the related publish list
        assertTrue(relatedList.getAllResources().isEmpty());
        // check the merged publish list
        assertEquals(pubList.getDeletedFolderList(), mergedList.getDeletedFolderList());
        assertEquals(pubList.getFolderList(), mergedList.getFolderList());
        assertEquals(pubList.getFileList(), mergedList.getFileList());
    }

    /**
     * Test publishing of related files in an unpublished folder.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishRelatedFilesInNewFolder() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing of related files in an unpublished folder");

        String folderName = "/testFolder/";
        String fileName = "/testFolder/testFile.gif";
        String srcName = "sourceTest.html";

        // create folder and file        
        cms.createResource(folderName, CmsResourceTypeFolder.RESOURCE_TYPE_ID);
        cms.createResource(fileName, CmsResourceTypeImage.getStaticTypeId());

        // create source file
        String content = CmsXmlPageFactory.createDocument(Locale.ENGLISH, CmsEncoder.ENCODING_UTF_8);
        CmsResource source = cms.createResource(
            srcName,
            CmsResourceTypeXmlPage.getStaticTypeId(),
            content.getBytes(CmsEncoder.ENCODING_UTF_8),
            null);

        CmsFile file = cms.readFile(srcName);
        CmsXmlPage page = CmsXmlPageFactory.unmarshal(cms, file);
        String element = "test";
        page.addValue(element, Locale.ENGLISH);

        // set the strong link
        content = "<img src='" + fileName + "'>";
        page.setStringValue(cms, element, Locale.ENGLISH, content);

        file.setContents(page.marshal());
        cms.writeFile(file);

        // get the publish list
        CmsPublishList pubList = OpenCms.getPublishManager().getPublishList(cms, source, false);
        // just check the publish list
        assertTrue(pubList.getDeletedFolderList().isEmpty());
        assertTrue(pubList.getFolderList().isEmpty());
        assertEquals(1, pubList.getFileList().size());

        // get the list of related resources
        CmsPublishList relatedList = OpenCms.getPublishManager().getRelatedResourcesToPublish(cms, pubList);
        // check the publish list has not been touched
        assertTrue(pubList.getDeletedFolderList().isEmpty());
        assertTrue(pubList.getFolderList().isEmpty());
        assertEquals(1, pubList.getFileList().size());
        // check the related publish list
        assertTrue(relatedList.getDeletedFolderList().isEmpty());
        assertEquals(1, relatedList.getFolderList().size());
        assertEquals(1, relatedList.getFileList().size());

        CmsPublishList mergedList = OpenCms.getPublishManager().mergePublishLists(cms, pubList, relatedList);
        // check the publish list has not been touched
        assertTrue(pubList.getDeletedFolderList().isEmpty());
        assertTrue(pubList.getFolderList().isEmpty());
        assertEquals(1, pubList.getFileList().size());
        // check the related publish list
        assertTrue(relatedList.getDeletedFolderList().isEmpty());
        assertEquals(1, relatedList.getFolderList().size());
        assertEquals(1, relatedList.getFileList().size());
        // check the merged publish list
        assertTrue(mergedList.getDeletedFolderList().isEmpty());
        assertEquals(1, mergedList.getFolderList().size());
        assertEquals(2, mergedList.getFileList().size());
    }

    /**
     * Test publishing a replaced file.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishReplacedFile() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing a replaced file");

        String resourcename = "testReplace.txt";
        byte[] orgCnt = "original content".getBytes();
        byte[] newCnt = "new content".getBytes();

        // create new file
        cms.createResource(resourcename, CmsResourceTypePlain.getStaticTypeId(), orgCnt, null);
        // assert offline
        assertContent(cms, resourcename, orgCnt);
        // publish
        OpenCms.getPublishManager().publishResource(cms, resourcename);
        OpenCms.getPublishManager().waitWhileRunning();

        // assert online
        cms.getRequestContext().setCurrentProject(cms.readProject(CmsProject.ONLINE_PROJECT_ID));
        assertContent(cms, resourcename, orgCnt);
        cms = getCmsObject();

        // replace
        cms.lockResource(resourcename);
        cms.replaceResource(resourcename, CmsResourceTypePlain.getStaticTypeId(), newCnt, null);
        // assert offline
        assertContent(cms, resourcename, newCnt);
        // publish again
        OpenCms.getPublishManager().publishResource(cms, resourcename);
        OpenCms.getPublishManager().waitWhileRunning();

        // assert online
        cms.getRequestContext().setCurrentProject(cms.readProject(CmsProject.ONLINE_PROJECT_ID));
        assertContent(cms, resourcename, newCnt);
    }

    /**
     * Test publishing a single resource with an iteration.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishResourceGalore() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing a single resource with an iteration");

        // publish directly with a single resource
        String resourcename = "index.html";

        cms.lockResource(resourcename);
        cms.setDateLastModified(resourcename, System.currentTimeMillis(), true);

        for (int i = 0; i < 10; i++) {
            OpenCms.getPublishManager().publishResource(cms, resourcename);
        }
        OpenCms.getPublishManager().waitWhileRunning();

        List pubHistory = OpenCms.getPublishManager().getPublishHistory();
        assertEquals(10, pubHistory.size());
        CmsPublishJobFinished pubJob = (CmsPublishJobFinished)pubHistory.get(0);
        assertEquals(1, pubJob.getSize());
        for (int i = 1; i < 10; i++) {
            pubJob = (CmsPublishJobFinished)pubHistory.get(i);
            assertEquals("pubJob: " + i, 0, pubJob.getSize());
        }
    }

    /**
     * Tests the publishing of resource property.<p>
     * 
     * take 2 siblings (s1 and s2):
     * - change a shared property of s1
     * - s2 will be marked as changed also
     * - direct publishing s1, should also change s2
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishResourceProperty() throws Throwable {

        CmsObject cms = getCmsObject();
        String source = "/folder1/page1.html";
        String sibling = "/folder1/sibling1.html";
        echo("testing the publishing of structure property");

        storeResources(cms, source);
        storeResources(cms, sibling);

        cms.lockResource(sibling);
        cms.writePropertyObject(sibling, new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, null, "test4"));
        cms.unlockResource(sibling);

        assertFilter(cms, sibling, OpenCmsTestResourceFilter.FILTER_WRITEPROPERTY);
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_WRITEPROPERTY);

        storeResources(cms, source);
        storeResources(cms, sibling);

        List pubList = OpenCms.getPublishManager().getPublishList(cms).getFileList();
        assertEquals(pubList.size(), 2);
        assertEquals(((CmsResource)pubList.get(0)).getRootPath(), cms.getRequestContext().addSiteRoot(source));
        assertEquals(((CmsResource)pubList.get(1)).getRootPath(), cms.getRequestContext().addSiteRoot(sibling));

        assertFilter(cms, cms.readResource(source), OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, cms.readResource(sibling), OpenCmsTestResourceFilter.FILTER_EQUAL);

        OpenCms.getPublishManager().publishResource(cms, source);
        OpenCms.getPublishManager().waitWhileRunning();

        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);
        assertFilter(cms, sibling, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);

        storeResources(cms, source);

        assertTrue(OpenCms.getPublishManager().getPublishList(cms).getFileList().isEmpty());
    }

    /**
     * Test publishing a resource with related resources with an iteration.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishResourceWithRelatedGalore() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publishing a resource with related resources with an iteration");

        // publish directly with a single resource and related resource
        String resourcename = "index.html";

        // touch everything, so the involved resources can be published
        cms.lockResource("/");
        cms.setDateLastModified("/", System.currentTimeMillis(), true);

        // get the list of involved files
        CmsPublishList pubList = OpenCms.getPublishManager().getPublishList(cms, cms.readResource(resourcename), false);
        CmsPublishList relatedList = OpenCms.getPublishManager().getRelatedResourcesToPublish(cms, pubList);
        pubList = OpenCms.getPublishManager().mergePublishLists(cms, pubList, relatedList);

        for (int i = 0; i < 10; i++) {
            // create a new publish list
            CmsPublishList publishList = OpenCms.getPublishManager().getPublishList(
                cms,
                cms.readResource(resourcename, CmsResourceFilter.ALL),
                false);
            // get the related resources
            CmsPublishList relResources = OpenCms.getPublishManager().getRelatedResourcesToPublish(cms, publishList);
            // merge the two publish lists
            publishList = OpenCms.getPublishManager().mergePublishLists(cms, publishList, relResources);
            // publish
            OpenCms.getPublishManager().publishProject(
                cms,
                new CmsShellReport(cms.getRequestContext().getLocale()),
                publishList);
        }
        OpenCms.getPublishManager().waitWhileRunning();

        List pubHistory = OpenCms.getPublishManager().getPublishHistory();
        assertEquals(10, pubHistory.size());
        CmsPublishJobFinished pubJob = (CmsPublishJobFinished)pubHistory.get(0);
        assertEquals(pubList.size(), pubJob.getSize());
        for (int i = 1; i < 10; i++) {
            pubJob = (CmsPublishJobFinished)pubHistory.get(i);
            assertEquals("pubJob: " + i, 0, pubJob.getSize());
        }
    }

    /**
     * Tests the publishing of structure/shared properties.<p>
     * 
     * take 2 siblings (s1 and s2):
     * - change a individual property of s1 
     * - change a shared property of s2 
     * - direct publishing s2, should not change s1
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishSiblings() throws Throwable {

        CmsObject cms = getCmsObject();
        String source = "/folder1/page1.html";
        String sibling = "/folder1/sibling1.html";
        echo("testing the publishing of structure/shared properties");

        storeResources(cms, source);
        storeResources(cms, sibling);

        cms.lockResource(sibling); // individual property
        cms.writePropertyObject(sibling, new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, "test2", null));
        cms.unlockResource(sibling);

        assertFilter(cms, sibling, OpenCmsTestResourceFilter.FILTER_WRITEPROPERTY);

        cms.lockResource(source); // shared property
        cms.writePropertyObject(source, new CmsProperty(CmsPropertyDefinition.PROPERTY_NAVTEXT, null, "test3"));
        cms.unlockResource(source);

        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_WRITEPROPERTY);

        storeResources(cms, source);
        storeResources(cms, sibling);

        List pubList = OpenCms.getPublishManager().getPublishList(cms).getFileList();
        assertEquals(pubList.size(), 2);
        assertEquals(((CmsResource)pubList.get(0)).getRootPath(), cms.getRequestContext().addSiteRoot(source));
        assertEquals(((CmsResource)pubList.get(1)).getRootPath(), cms.getRequestContext().addSiteRoot(sibling));

        assertFilter(cms, cms.readResource(source), OpenCmsTestResourceFilter.FILTER_EQUAL);
        assertFilter(cms, cms.readResource(sibling), OpenCmsTestResourceFilter.FILTER_EQUAL);

        OpenCms.getPublishManager().publishResource(cms, source);
        OpenCms.getPublishManager().waitWhileRunning();

        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);
        assertFilter(cms, sibling, OpenCmsTestResourceFilter.FILTER_EQUAL);

        storeResources(cms, source);

        pubList = OpenCms.getPublishManager().getPublishList(cms).getFileList();
        assertEquals(pubList.size(), 1);
        assertEquals(((CmsResource)pubList.get(0)).getRootPath(), cms.getRequestContext().addSiteRoot(sibling));
        assertFilter(cms, cms.readResource(sibling), OpenCmsTestResourceFilter.FILTER_EQUAL);

        OpenCms.getPublishManager().publishResource(cms, sibling);
        OpenCms.getPublishManager().waitWhileRunning();

        assertFilter(cms, sibling, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_EQUAL);
    }

    /**
     * Tests the publishing of structure property.<p>
     * 
     * take 2 siblings (s1 and s2):
     * - change a individual property of s1
     * - s2 remains unchanged
     * - direct publishing s1, should not change s2
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishStructureProperty() throws Throwable {

        CmsObject cms = getCmsObject();
        String source = "/folder1/page1.html";
        String sibling = "/folder1/sibling1.html";
        echo("testing the publishing of structure property");

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

        // be sure everything is published
        cms.unlockProject(cms.getRequestContext().currentProject().getUuid());
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();

        storeResources(cms, source);
        storeResources(cms, sibling);

        cms.lockResource(sibling);
        cms.writePropertyObject(sibling, new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, "test1", null));
        cms.unlockResource(sibling);

        assertFilter(cms, sibling, OpenCmsTestResourceFilter.FILTER_WRITEPROPERTY);
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_SIBLING_PROPERTY);

        storeResources(cms, source);
        storeResources(cms, sibling);

        List pubList = OpenCms.getPublishManager().getPublishList(cms).getFileList();
        assertEquals(pubList.size(), 1);
        assertEquals(((CmsResource)pubList.get(0)).getRootPath(), cms.getRequestContext().addSiteRoot(sibling));
        assertFilter(cms, cms.readResource(sibling), OpenCmsTestResourceFilter.FILTER_EQUAL);

        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();

        assertFilter(cms, sibling, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_EQUAL);
    }

    /**
     * Test publishing a temporary project.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishTemporaryProject() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing publish temporary project");

        String source = "/folder1/testfile.txt";
        CmsProject onlineProject = cms.readProject("Online");
        CmsProject offlineProject = cms.readProject("Offline");

        //create a new temp project
        CmsProject tempProject = cms.createProject(
            "deleteme",
            "Temp project to be deleted after publish",
            "Users",
            "Projectmanagers",
            CmsProject.PROJECT_TYPE_TEMPORARY);
        cms.copyResourceToProject("/");
        cms.getRequestContext().setCurrentProject(tempProject);

        // now create a new resource
        cms.createResource(source, CmsResourceTypePlain.getStaticTypeId());
        cms.unlockResource(source);
        storeResources(cms, source);

        cms.readResource(source);

        //publish the project
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();

        // now try to read the resource
        try {
            cms.readResource(source);
        } catch (CmsException e) {
            fail("Resource "
                + source
                + " not found in project "
                + cms.getRequestContext().currentProject().toString()
                + ":"
                + e);
        }

        // now try to read the resource in the online project
        cms.getRequestContext().setCurrentProject(onlineProject);
        try {
            cms.readResource(source);
        } catch (CmsException e) {
            fail("Resource "
                + source
                + " not found in project "
                + cms.getRequestContext().currentProject().toString()
                + ":"
                + e);
        }

        // check if the state of the resource is ok
        assertFilter(cms, source, OpenCmsTestResourceFilter.FILTER_PUBLISHRESOURCE);

        // check if the file in the offline project is unchancged now
        cms.getRequestContext().setCurrentProject(offlineProject);
        assertState(cms, source, CmsResource.STATE_UNCHANGED);

        // check if the project is deleted
        try {
            cms.readProject("deleteme");
            fail("Temporary project still existing");
        } catch (CmsException e) {
            // to nothing, this exception must be thrown
        }

    }

    /**
     * Returns a project for testing purposes.<p>
     * 
     * @param cms the cms object
     * 
     * @return a project for testing purposes
     */
    private CmsProject getTestProject(CmsObject cms) {

        CmsProject project = null;
        try {
            project = cms.readProject("Test");
        } catch (Exception e) {
            try {
                project = cms.createProject("Test", "", "Users", "Administrators");
            } catch (Exception ee) {
                // noop
            }
        }
        return project;
    }
}
