/*
 * File   : $Source: /usr/local/cvs/opencms/test/org/opencms/publish/TestPublishManager.java,v $
 * Date   : $Date: 2008-02-27 12:05:49 $
 * Version: $Revision: 1.5 $
 *
 * 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.publish;

import org.opencms.db.CmsLoginMessage;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsResource;
import org.opencms.lock.CmsLockException;
import org.opencms.lock.CmsLockType;
import org.opencms.main.CmsContextInfo;
import org.opencms.main.OpenCms;
import org.opencms.security.CmsPermissionSet;
import org.opencms.security.CmsSecurityException;
import org.opencms.security.I_CmsPrincipal;
import org.opencms.test.OpenCmsTestCase;
import org.opencms.test.OpenCmsTestProperties;

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

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

/**
 * Unit tests for the publish manager.<p>
 * 
 * @author Michael Moossen 
 * 
 * @version $Revision: 1.5 $
 */
public class TestPublishManager extends OpenCmsTestCase {

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

        suite.addTest(new TestPublishManager("testPublishReport"));
        suite.addTest(new TestPublishManager("testAbortJob"));
        suite.addTest(new TestPublishManager("testRunning"));
        suite.addTest(new TestPublishManager("testStop"));
        suite.addTest(new TestPublishManager("testListener"));
        suite.addTest(new TestPublishManager("testInitialization1"));
        suite.addTest(new TestPublishManager("testInitialization2"));

        TestSetup wrapper = new TestSetup(suite) {

            protected void setUp() {

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

            protected void tearDown() {

                removeOpenCms();
            }
        };

        return wrapper;
    }

    /**
     * Test aborting an enqueued publish job.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testAbortJob() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing aborting an enqueued publish job");

        String source = "/folder2/subfolder21/image1.gif";
        String destination = "/folder1/image1_new"; // + i + ".gif";

        assertFalse(OpenCms.getPublishManager().isRunning());
        // stop the publish engine in order to perform the checks
        OpenCms.getPublishManager().stopPublishing();

        // copy and publish n new resources
        int max = 10;
        for (int i = 0; i < max; i++) {
            cms.copyResource(source, destination + (i + 1) + ".gif", CmsResource.COPY_AS_NEW);
            OpenCms.getPublishManager().publishResource(cms, destination + (i + 1) + ".gif");
        }

        // get the last enqueued publish job
        List queue = OpenCms.getPublishManager().getPublishQueue();
        CmsPublishJobEnqueued publishJob = (CmsPublishJobEnqueued)queue.get(queue.size() - 1);

        // login another user
        cms.loginUser("test1", "test1");
        try {
            // try to abort the last enqueued publish job
            OpenCms.getPublishManager().abortPublishJob(cms, publishJob, true);
            fail("should not be possible to abort a publish job by another user");
        } catch (CmsSecurityException e) {
            // ok, ignore
        }
        // login again as admin, keep online project
        cms.loginUser("Admin", "admin");

        // abort the last enqueued publish job
        OpenCms.getPublishManager().abortPublishJob(cms, publishJob, true);

        // try to abort the publish job again
        try {
            OpenCms.getPublishManager().abortPublishJob(cms, publishJob, true);
            fail("should not be possible to abort a publish job that has been already aborted");
        } catch (CmsPublishException e) {
            // ok, ignore
        }

        // start the background publishing again
        OpenCms.getPublishManager().startPublishing();
        // wait until everything get published
        OpenCms.getPublishManager().waitWhileRunning();

        // check that all files except the last one has been published
        for (int i = 1; i < max; i++) {
            assertState(cms, destination + i + ".gif", CmsResource.STATE_UNCHANGED);
        }
        // the last one should not be published
        assertFalse(cms.existsResource(destination + max + ".gif"));

        // switch to the "Offline" project
        cms.getRequestContext().setCurrentProject(cms.readProject("Offline"));

        // check that all files except the last one has been published
        for (int i = 1; i < max; i++) {
            assertState(cms, destination + i + ".gif", CmsResource.STATE_UNCHANGED);
            // and unlocked
            assertLock(cms, destination + i + ".gif", CmsLockType.UNLOCKED);
        }
        // the last one should not be published
        assertState(cms, destination + max + ".gif", CmsResource.STATE_NEW);
        // and unlocked
        assertLock(cms, destination + max + ".gif", CmsLockType.UNLOCKED);

        // clean up
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Tests the reinitialization of the publish manager/engine.<p> 
     * 
     * @throws Throwable if something goes wrong
     */
    public void testInitialization1() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing initializing the publish manager/engine");

        // publish engine should not run currently
        assertFalse(OpenCms.getPublishManager().isRunning());
        // stop publishing
        OpenCms.getPublishManager().stopPublishing();

        String source = "/folder2/subfolder21/image1.gif";
        String destination1 = "/testInitialization1_"; // + i + ".gif";

        if (!cms.getLock(source).isNullLock()) {
            cms.unlockResource(source);
        }

        int max = 12; // be sure that it is the same as the system/publishhistory/history-size value + 2        
        // copy n new resources
        for (int i = 0; i < max; i++) {
            cms.copyResource(source, destination1 + (i + 1) + ".gif", CmsResource.COPY_AS_NEW);
            OpenCms.getPublishManager().publishResource(cms, destination1 + (i + 1) + ".gif");
        }

        // store current publishQueue
        List oldQueue = OpenCms.getPublishManager().getPublishQueue();
        // store current publish history
        List oldHistory = OpenCms.getPublishManager().getPublishHistory();

        // leads to reloading the queue and history data from the database
        OpenCms.getPublishManager().initialize(cms);

        // get reinitialized queue and history
        List newQueue = OpenCms.getPublishManager().getPublishQueue();
        List newHistory = OpenCms.getPublishManager().getPublishHistory();

        // compare old and new queue
        echo("Checking revived publish queue (" + oldQueue.size() + " items)");
        if (newQueue.size() != oldQueue.size()) {
            fail("Old and new queue have not the same size: Expected <"
                + oldQueue.size()
                + ">, was <"
                + newQueue.size()
                + ">");
        }
        Iterator n = newQueue.iterator();
        Iterator o = oldQueue.iterator();
        while (n.hasNext() && o.hasNext()) {
            CmsPublishJobEnqueued newJob = (CmsPublishJobEnqueued)n.next();
            CmsPublishJobEnqueued oldJob = (CmsPublishJobEnqueued)o.next();
            assertEquals(newJob, oldJob, true, true);
        }

        // compare old and new history  
        echo("Checking revived publish history (" + oldHistory.size() + " items)");
        if (newHistory.size() != oldHistory.size()) {
            fail("Old and new history have not the same size: Expected <"
                + oldHistory.size()
                + ">, was <"
                + newHistory.size()
                + ">");
        }
        n = newHistory.iterator();
        o = oldHistory.iterator();
        while (n.hasNext() && o.hasNext()) {
            CmsPublishJobFinished newJob = (CmsPublishJobFinished)n.next();
            CmsPublishJobFinished oldJob = (CmsPublishJobFinished)o.next();
            assertEquals(newJob, oldJob, false, true);
        }

        // start the publish engine and wait until all jobs are published
        OpenCms.getPublishManager().startPublishing();
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Tests the reinitialization of the publish manager/engine.<p> 
     * 
     * @throws Throwable if something goes wrong
     */
    public void testInitialization2() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing initializing the publish manager/engine");

        // publish engine should not run currently
        assertFalse(OpenCms.getPublishManager().isRunning());
        // stop publishing
        OpenCms.getPublishManager().stopPublishing();

        String source = "/folder2/subfolder21/image1.gif";
        String destination2 = "/testInitialization2_"; // + i + ".gif";

        if (!cms.getLock(source).isNullLock()) {
            cms.unlockResource(source);
        }

        int max = 12; // be sure that it is the same as the system/publishhistory/history-size value + 2
        // create a larger project
        for (int i = 0; i < max; i++) {
            cms.copyResource(source, destination2 + (i + 1) + ".gif", CmsResource.COPY_AS_NEW);
        }
        OpenCms.getPublishManager().publishProject(cms);

        // store current publishQueue
        List oldQueue = OpenCms.getPublishManager().getPublishQueue();

        // add an event listener used to restart the engine when ther job was started
        OpenCms.getPublishManager().addPublishListener(new TestPublishEventListener2(cms));

        // now start publishing again and reinitialize the publish manager and engine while it is running
        OpenCms.getPublishManager().startPublishing();
        OpenCms.getPublishManager().waitWhileRunning();

        // get reinitialized queue and history
        List newQueue = OpenCms.getPublishManager().getPublishQueue();
        List newHistory = OpenCms.getPublishManager().getPublishHistory();

        // the project should not be in the publish queue anymore
        assertEquals(1, oldQueue.size());
        assertEquals(0, newQueue.size());

        // but it should be stored as last entry in the history
        CmsPublishJobEnqueued jobInQueue = (CmsPublishJobEnqueued)oldQueue.get(0);
        CmsPublishJobFinished jobInHistory = (CmsPublishJobFinished)newHistory.get(newHistory.size() - 1);
        assertEquals(jobInQueue, jobInHistory, false, false);

        // and it should be aborted
    }

    /**
     * Test the publish event listener.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testListener() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing the publish event listener");

        String source = "/folder2/subfolder21/image1.gif";
        String destination = "/testListener_"; // + i + ".gif";

        // copy n new resources
        int max = 12; // be sure that it is the same as the system/publishhistory/history-size value + 2
        for (int i = 0; i < max; i++) {
            cms.copyResource(source, destination + (i + 1) + ".gif", CmsResource.COPY_AS_NEW);
        }

        // set the listener for the first job
        TestPublishEventListener firstListener = new TestPublishEventListener(cms.readResource(destination + "1.gif"));
        OpenCms.getPublishManager().addPublishListener(firstListener);

        // set the listener for the last job
        TestPublishEventListener lastListener = new TestPublishEventListener(cms.readResource(destination
            + max
            + ".gif"));
        OpenCms.getPublishManager().addPublishListener(lastListener);

        // remember the time
        long preEnqueueTime = System.currentTimeMillis();

        assertFalse(OpenCms.getPublishManager().isRunning());
        // stop the publish engine in order to perform the checks
        OpenCms.getPublishManager().stopPublishing();

        // publish(enqueue) n new resources
        for (int i = 0; i < max; i++) {
            OpenCms.getPublishManager().publishResource(cms, destination + (i + 1) + ".gif");
        }
        assertFalse(OpenCms.getPublishManager().isRunning());

        // get the last enqueued publish job
        List queue = OpenCms.getPublishManager().getPublishQueue();
        CmsPublishJobEnqueued publishJob = (CmsPublishJobEnqueued)queue.get(queue.size() - 1);
        // abort it
        OpenCms.getPublishManager().abortPublishJob(cms, publishJob, true);

        // start the background publishing again
        OpenCms.getPublishManager().startPublishing();
        // wait until finished
        OpenCms.getPublishManager().waitWhileRunning();

        // the first job was not aborted
        assertEquals(0, firstListener.getAborted());
        // but it was enqueued
        assertTrue(preEnqueueTime <= firstListener.getEnqueued());
        // and started
        assertTrue(firstListener.getEnqueued() <= firstListener.getStarted());
        // and finished
        assertTrue(firstListener.getStarted() <= firstListener.getFinished());
        // and removed (depending on history size)
        // assertTrue(firstListener.getFinished() <= firstListener.getRemoved());

        // the last job was not started
        assertEquals(0, lastListener.getStarted());
        // nor finished
        assertEquals(0, lastListener.getFinished());
        // but it was enqueued
        assertTrue(preEnqueueTime <= lastListener.getEnqueued());
        // and aborted
        assertTrue(lastListener.getEnqueued() <= lastListener.getAborted());
        // and removed
        assertTrue(lastListener.getAborted() <= lastListener.getRemoved());

        // check the jobs in queue counters
        int[] firstJobsInQueue = firstListener.getJobsInQueueCounter();
        int[] lastJobsInQueue = lastListener.getJobsInQueueCounter();
        // at enqueue time of first job: queue should contain only the job
        assertTrue(firstJobsInQueue[0] == 1);
        // at enqueue time of last job: queue should contain  all jobs
        assertTrue(lastJobsInQueue[0] == max);
        // at start time of first job: queue should contain all jobs but not the job itself and not the aborted job
        assertTrue(firstJobsInQueue[1] == max - 2);
        // last job is never started
        assertTrue(lastJobsInQueue[1] == 0);
        // at finish time of first job: queue should still contain all jobs but not the job itself and not the aborted job
        assertTrue(firstJobsInQueue[2] == max - 2);
        // last job is never finished
        assertTrue(lastJobsInQueue[2] == 0);
        // first job is not aborted
        assertTrue(firstJobsInQueue[3] == 0);
        // at abort time of last job: queue should contain all jobs including the aborted job
        assertTrue(lastJobsInQueue[3] == max);
        // first job is not removed since it is still in the job history
        assertTrue(firstJobsInQueue[4] == 0);
        // at remove time of the last job: queue should contain all jobs but not the aborted job
        assertTrue(lastJobsInQueue[4] == max - 1);

        // check the jobs in history counters
        int[] firstJobsInHistory = firstListener.getJobsInHistoryCounter();
        int[] lastJobsInHistory = lastListener.getJobsInHistoryCounter();
        // at enqueue/start/finish time of the first job, the history is full (10 Elements)
        assertTrue(firstJobsInHistory[0] == 10);
        assertTrue(firstJobsInHistory[1] == 10);
        assertTrue(firstJobsInHistory[2] == 10);
        assertTrue(firstJobsInHistory[3] == 0);
        assertTrue(firstJobsInHistory[4] == 9);
        // when the first element is removed, the history queue has one free slot
        // at start/abort/remove time of the last job, the history is unchanged
        assertTrue(lastJobsInHistory[0] == 10);
        assertTrue(lastJobsInHistory[1] == 0);
        assertTrue(lastJobsInHistory[2] == 0);
        assertTrue(lastJobsInHistory[3] == 10);
        assertTrue(lastJobsInHistory[4] == 10);

        // clean up
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }

    /**
     * Tests the publish report stored in the database.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testPublishReport() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing the publish report");

        String source = "/folder2/subfolder21/image1.gif";
        String destination = "/testReport_1.gif";

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

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

        List history = OpenCms.getPublishManager().getPublishHistory();
        CmsPublishJobFinished publishJob = (CmsPublishJobFinished)history.get(history.size() - 1);
        String reportContents = new String(OpenCms.getPublishManager().getReportContents(publishJob));

        // check if the report states that the destination was published successfully
        if (!reportContents.matches("(?s)" + "(.*)" + destination + "(.*)" + "o.k." + "(.*)")) {
            System.err.println(reportContents);
            fail("publish report contains errors");
        }
    }

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

        CmsObject cms = getCmsObject();
        echo("Testing publishing process");

        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";

        // 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);

        assertFalse(OpenCms.getPublishManager().isRunning());
        // stop the publish engine in order to perform the checks
        OpenCms.getPublishManager().stopPublishing();
        // now publish (enqeue) the project
        OpenCms.getPublishManager().publishProject(cms);
        assertFalse(OpenCms.getPublishManager().isRunning());

        assertLock(cms, destination1, CmsLockType.PUBLISH);
        try {
            cms.lockResource(destination1);
            fail("it should not be possible to lock a resource that is waiting to be published");
        } catch (CmsLockException e) {
            // ok, ignore
        }
        assertLock(cms, destination2, CmsLockType.PUBLISH);
        try {
            cms.lockResource(destination2);
            fail("it should not be possible to lock a resource that is waiting to be published");
        } catch (CmsLockException e) {
            // ok, ignore
        }
        assertLock(cms, destination3, CmsLockType.PUBLISH);
        try {
            cms.lockResource(destination3);
            fail("it should not be possible to lock a resource that is waiting to be published");
        } catch (CmsLockException e) {
            // ok, ignore
        }
        assertLock(cms, destination4, CmsLockType.PUBLISH);
        try {
            cms.lockResource(destination4);
            fail("it should not be possible to lock a resource that is waiting to be published");
        } catch (CmsLockException e) {
            // ok, ignore
        }

        // now start the background publishing process
        OpenCms.getPublishManager().startPublishing();
        // wait until the background publishing is finished
        OpenCms.getPublishManager().waitWhileRunning();

        // now check the locks again
        assertLock(cms, destination1, CmsLockType.UNLOCKED);
        assertLock(cms, destination2, CmsLockType.UNLOCKED);
        assertLock(cms, destination3, CmsLockType.UNLOCKED);
        assertLock(cms, destination4, CmsLockType.UNLOCKED);
    }

    /**
     * Test stopping/starting the publish engine.<p>
     * 
     * @throws Throwable if something goes wrong
     */
    public void testStop() throws Throwable {

        CmsObject cms = getCmsObject();
        echo("Testing stopping/starting the publish engine");

        String source = "/folder2/subfolder21/image1.gif";
        String destination1 = "/folder2/subfolder22/image1_new1.gif";
        String destination2 = "/folder2/subfolder22/image1_new2.gif";
        String destination3 = "/folder2/subfolder22/image1_new3.gif";
        String destination4 = "/folder2/subfolder22/image1_new4.gif";

        // copy and publish a new resource 
        cms.copyResource(source, destination1, CmsResource.COPY_AS_NEW);
        OpenCms.getPublishManager().publishResource(cms, destination1);

        // create another cms user instance
        CmsContextInfo contextInfo = new CmsContextInfo("test1");
        contextInfo.setProjectName(cms.getRequestContext().currentProject().getName());
        contextInfo.setSiteRoot(cms.getRequestContext().getSiteRoot());
        CmsObject ucms = OpenCms.initCmsObject(cms, contextInfo);

        // stop the publish engine by disabling the login
        OpenCms.getLoginManager().setLoginMessage(cms, new CmsLoginMessage("test", true));

        // copy and publish a new resource 
        cms.copyResource(source, destination2, CmsResource.COPY_AS_NEW);
        // should still work since i am the admin
        OpenCms.getPublishManager().publishResource(cms, destination2);
        // wait until publish engine is finished
        OpenCms.getPublishManager().waitWhileRunning();

        // create new resources
        cms.copyResource(source, destination3, CmsResource.COPY_AS_NEW);
        cms.copyResource(source, destination4, CmsResource.COPY_AS_NEW);

        // give the user publish permission
        cms.chacc(destination3, I_CmsPrincipal.PRINCIPAL_USER, "test1", new CmsPermissionSet(
            CmsPermissionSet.PERMISSION_FULL,
            0).getPermissionString());
        cms.chacc(destination4, I_CmsPrincipal.PRINCIPAL_USER, "test1", new CmsPermissionSet(
            CmsPermissionSet.PERMISSION_FULL,
            0).getPermissionString());

        // and unlock the resources in order to let the other user publish them
        cms.unlockResource(destination3);
        cms.unlockResource(destination4);

        // publish a new resource 
        try {
            OpenCms.getPublishManager().publishResource(ucms, destination3);
            fail("a user without administration rights should not be able to publish when the publish engine is disabled");
        } catch (CmsPublishException e) {
            // ok, ignore
        }

        // the resource should not have a publish lock left (was an error)
        assertTrue(ucms.getLock(destination3).isUnlocked());

        // re-enable the login (and implicitly the publish engine)
        OpenCms.getLoginManager().removeLoginMessage(cms);
        // publish engine should not run currently
        assertFalse(OpenCms.getPublishManager().isRunning());

        // try again, it should work now
        OpenCms.getPublishManager().publishResource(ucms, destination4);
        // wait until resource is published
        OpenCms.getPublishManager().waitWhileRunning();

        // third resource was not published, while fourth resource was
        assertState(cms, destination3, CmsResource.STATE_NEW);
        assertState(cms, destination4, CmsResource.STATE_UNCHANGED);

        // clean up
        OpenCms.getPublishManager().publishProject(cms);
        OpenCms.getPublishManager().waitWhileRunning();
    }
}