Package org.fcrepo.server.journal.readerwriter.multifile

Source Code of org.fcrepo.server.journal.readerwriter.multifile.TestLockingFollowingJournalReader

/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package org.fcrepo.server.journal.readerwriter.multifile;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.fcrepo.common.Constants;
import org.fcrepo.server.Context;
import org.fcrepo.server.errors.ServerException;
import org.fcrepo.server.journal.JournalConstants;
import org.fcrepo.server.journal.JournalConsumer;
import org.fcrepo.server.journal.MockJournalRecoveryLog;
import org.fcrepo.server.journal.MockServerForJournalTesting;
import org.fcrepo.server.journal.ServerInterface;
import org.fcrepo.server.management.MockManagementDelegate;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



public class TestLockingFollowingJournalReader
        implements Constants, JournalConstants, MultiFileJournalConstants {

    private static Logger LOGGER =
        LoggerFactory.getLogger(TestLockingFollowingJournalReader.class);
   
    @Rule
    public TemporaryFolder tmpFolder = new TemporaryFolder();

    private static final int WAIT_INTERVAL = 5;

    private static final String JOURNAL_FILENAME_PREFIX = "unit";

    private static final String DUMMY_HASH_VALUE = "Dummy Hash";

    private File journalDirectory;

    private File archiveDirectory;

    private File lockRequestFile;

    private File lockAcceptedFile;

    private Map<String, String> parameters;

    private ServerInterface server;

    private final String role = "DumbGrunt";

    private MyMockManagementDelegate delegate;

    private int initialNumberOfThreads;

    @Before
    public void setUp() throws Exception {
        journalDirectory = createTempDirectory("fedoraTestingJournalFiles");
        archiveDirectory = createTempDirectory("fedoraTestingArchiveFiles");

        lockRequestFile =
                new File(journalDirectory.getPath() + File.separator
                        + "lockRequested");
        lockRequestFile.delete();

        lockAcceptedFile =
                new File(journalDirectory.getPath() + File.separator
                        + "lockAccepted");
        lockAcceptedFile.delete();

        delegate = new MyMockManagementDelegate();

        server = new MockServerForJournalTesting(delegate, DUMMY_HASH_VALUE);

        parameters = new HashMap<String, String>();
        parameters.put(PARAMETER_JOURNAL_RECOVERY_LOG_CLASSNAME,
                       MockJournalRecoveryLog.class.getName());
        parameters.put(PARAMETER_JOURNAL_READER_CLASSNAME,
                       LockingFollowingJournalReader.class.getName());
        parameters.put(PARAMETER_JOURNAL_DIRECTORY, journalDirectory.getPath());
        parameters.put(PARAMETER_ARCHIVE_DIRECTORY, archiveDirectory.getPath());
        parameters.put(PARAMETER_FOLLOW_POLLING_INTERVAL, "1");
        parameters.put(PARAMETER_JOURNAL_FILENAME_PREFIX,
                       JOURNAL_FILENAME_PREFIX);
        parameters.put(PARAMETER_LOCK_REQUESTED_FILENAME, lockRequestFile
                .getPath());
        parameters.put(PARAMETER_LOCK_ACCEPTED_FILENAME, lockAcceptedFile
                .getPath());

        initialNumberOfThreads = getNumberOfCurrentThreads();
    }
   
    @After
    public void cleanUp() throws Exception {
        delegate.reset();
        if (lockRequestFile.delete()){
            LOGGER.info("Cleaned up lockRequestFile file at {}", lockRequestFile.getPath());
        }
        if (lockAcceptedFile.delete()){
            LOGGER.info("Cleaned up lockAcceptedFile file at {}", lockAcceptedFile.getPath());
        }

        for (File file: journalDirectory.listFiles()) {
            boolean deleted = file.delete();
            LOGGER.info("{} Cleaned up a journalDirectory file: {}",
                    deleted, file.getPath());
        }
        for (File file: archiveDirectory.listFiles()) {
            boolean deleted = file.delete();
            LOGGER.info("{} Cleaned up a archiveDirectory file: {}",
                    deleted, file.getPath());
        }
    }

    /**
     * Create 3 files and watch it process all of them
     */
    @Test
    public void testSimpleNoLocking() {
        try {
            // create 3 files, each with an ingest
            createJournalFileFromString(getSimpleIngestString());
            createJournalFileFromString(getSimpleIngestString());
            createJournalFileFromString(getSimpleIngestString());

            // create the JournalConsumer and run it.
            JournalConsumer consumer =
                    new JournalConsumer(parameters, role, server);
            startConsumerThread(consumer);
            waitWhileThreadRuns(WAIT_INTERVAL);
            consumer.shutdown();

            assertEquals("Expected to see 3 ingests", 3, delegate
                    .getCallCount());
            assertEquals("Journal files not all gone",
                         0,
                         howManyFilesInDirectory(journalDirectory));
            assertEquals("Wrong number of archive files",
                         3,
                         howManyFilesInDirectory(archiveDirectory));
        } catch (Throwable e) {
            processException(e);
        }
    }

    /**
     * A lock request created before startup will prevent processing. When the
     * request is removed, processing will occur.
     */
    @Ignore
    public void testLockBeforeStartingAndResume() throws Exception {
            // create 3 files, each with an ingest, and create a lock request.
            createJournalFileFromString(getSimpleIngestString());
            createJournalFileFromString(getSimpleIngestString());
            createJournalFileFromString(getSimpleIngestString());
            createLockRequest();

            // create the JournalConsumer and run it.
            JournalConsumer consumer =
                    new JournalConsumer(parameters, role, server);
            startConsumerThread(consumer);

            // we should see the lock accepted and no processing going on.
            waitForLockAccepted();
            waitWhileThreadRuns(WAIT_INTERVAL);
            assertEquals("Journal files should not be processed", 0, delegate
                    .getCallCount());
            assertEquals("Journal files should not be processed",
                         3,
                         howManyFilesInDirectory(journalDirectory));
            assertEquals("Journal files should not be processed",
                         0,
                         howManyFilesInDirectory(archiveDirectory));
            int lockMessageIndex = assertLockMessageInLog();

            // remove the request. We should see the lock released and
            // processing should run to completion.
            removeLockRequest();
            waitForLockReleased();
            waitWhileThreadRuns(WAIT_INTERVAL);
            consumer.shutdown();

            assertEquals("Expected to see 3 ingests", 3, delegate
                    .getCallCount());
            assertEquals("Journal files not all gone",
                         0,
                         howManyFilesInDirectory(journalDirectory));
            assertEquals("Wrong number of archive files",
                         3,
                         howManyFilesInDirectory(archiveDirectory));
            assertUnlockMessageInLog(lockMessageIndex);
    }

    /**
     * A lock request created while a file is in progress, which should prevent
     * further processing until it is removed.
     */
    @Test
    public void testLockWhileProcessingAndResume() throws Exception {
            // create 3 files, each with an ingest
            createJournalFileFromString(getSimpleIngestString());
            createJournalFileFromString(getSimpleIngestString());
            createJournalFileFromString(getSimpleIngestString());

            // a lock request will be created while the second file is being
            // processed.
            delegate.setIngestOperation(new LockAfterSecondIngest());

            // create the JournalConsumer and run it.
            JournalConsumer consumer =
                    new JournalConsumer(parameters, role, server);
            startConsumerThread(consumer);

            // we should see the lock accepted and processing stop after the
            // second file.
            waitForLockAccepted();
            waitWhileThreadRuns(WAIT_INTERVAL);
            assertEquals("We should stop after the second ingest", 2, delegate
                    .getCallCount());
            assertEquals("One Journal file should not be processed",
                         1,
                         howManyFilesInDirectory(journalDirectory));
            assertEquals("Only two Journal files should be processed",
                         2,
                         howManyFilesInDirectory(archiveDirectory));
            int lockMessageIndex = assertLockMessageInLog();

            // remove the request. We should see the lock released and
            // processing should run to completion.
            removeLockRequest();
            waitForLockReleased();
            waitWhileThreadRuns(WAIT_INTERVAL);
            consumer.shutdown();

            assertEquals("Expected to see 3 ingests", 3, delegate
                    .getCallCount());
            assertEquals("Journal files not all gone",
                         0,
                         howManyFilesInDirectory(journalDirectory));
            assertEquals("Wrong number of archive files",
                         3,
                         howManyFilesInDirectory(archiveDirectory));
            assertUnlockMessageInLog(lockMessageIndex);
    }
   
    @Ignore
    public void testSimpleFirst() throws Exception {
        testSimpleNoLocking();
        cleanUp();
        setUp();
        testLockWhileProcessingAndResume();
    }

    @Ignore
    public void testSimpleLast() throws Exception {
        testLockWhileProcessingAndResume();
        cleanUp();
        setUp();
        testSimpleNoLocking();
    }

    /**
     * A lock request created while the system if polling, which should prevent
     * further processing until it is removed. Create 1 files and watch it
     * process all of them. Create a lock and wait for the ack. Create a 2nd
     * file, and it will not be processed. Remove the lock; ack is removed and
     * last file is processed.
     */
    @Ignore
    public void testLockWhilePollingAndResume() {
        try {
            // create 1 file, with an ingest
            createJournalFileFromString(getSimpleIngestString());

            // create the JournalConsumer and run it.
            JournalConsumer consumer =
                    new JournalConsumer(parameters, role, server);
            startConsumerThread(consumer);

            // the file should be processed and we being polling.
            waitWhileThreadRuns(WAIT_INTERVAL);
            assertEquals("The first file should have been processed.",
                         1,
                         delegate.getCallCount());
            assertEquals("The first file should have been processed.",
                         0,
                         howManyFilesInDirectory(journalDirectory));
            assertEquals("The first file should have been processed.",
                         1,
                         howManyFilesInDirectory(archiveDirectory));

            // create a lock request and wait for the acceptance.
            createLockRequest();
            waitForLockAccepted();

            // create another Journal file, but it won't be processed.
            createJournalFileFromString(getSimpleIngestString());
            waitWhileThreadRuns(WAIT_INTERVAL);
            assertEquals("The second file should not have been processed.",
                         1,
                         delegate.getCallCount());
            assertEquals("The second file should not have been processed.",
                         1,
                         howManyFilesInDirectory(journalDirectory));
            assertEquals("The second file should not have been processed.",
                         1,
                         howManyFilesInDirectory(archiveDirectory));
            int lockMessageIndex = assertLockMessageInLog();

            // remove the lock and the file is processed.
            removeLockRequest();
            waitForLockReleased();
            waitWhileThreadRuns(WAIT_INTERVAL);
            consumer.shutdown();

            assertEquals("Expected to see 2 ingests", 2, delegate
                    .getCallCount());
            assertEquals("Journal files not all gone",
                         0,
                         howManyFilesInDirectory(journalDirectory));
            assertEquals("Wrong number of archive files",
                         2,
                         howManyFilesInDirectory(archiveDirectory));
            assertUnlockMessageInLog(lockMessageIndex);
        } catch (Throwable e) {
            processException(e);
        }
    }

    private void createLockRequest() throws IOException {
        lockRequestFile.createNewFile();
    }

    private void removeLockRequest() {
        lockRequestFile.delete();
    }

    private int howManyFilesInDirectory(File directory) {
        return MultiFileJournalHelper
                .getSortedArrayOfJournalFiles(directory,
                                              JOURNAL_FILENAME_PREFIX).length;
    }

    /**
     * Wait until the JournalConsumerThread stops, or until the time limit
     * expires, whichever comes first.
     */
    private void waitWhileThreadRuns(int maxSecondsToWait) {
        for (int i = 0; i < maxSecondsToWait; i++) {
            if (getNumberOfCurrentThreads() == initialNumberOfThreads) {
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Wait until the lock is accepted, or until the time runs out. If the
     * latter, complain.
     */
    private void waitForLockAccepted() {
        int maxWait = 10;
        for (int i = 0; i < maxWait; i++) {
            if (lockAcceptedFile.exists()) {
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        fail("Lock was not accepted after " + maxWait + " seconds.");
    }

    /**
     * Wait until the lock is released, or until the time runs out. If the
     * latter, complain.
     */
    private void waitForLockReleased() {
        int maxWait = 10;
        for (int i = 0; i < maxWait; i++) {
            if (!lockAcceptedFile.exists()) {
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        fail("Lock was not released after " + maxWait + " seconds.");
    }

    /**
     * Set the ManagementDelegate into the JournalConsumer, which will create
     * the JournalConsumerThread.
     */
    private void startConsumerThread(JournalConsumer consumer) {
        consumer.setManagementDelegate(delegate);
    }

    private int getNumberOfCurrentThreads() {
        int i =
                Thread.currentThread().getThreadGroup()
                        .enumerate(new Thread[500]);
        return i;
    }

    private void createJournalFileFromString(String text) throws IOException {
        File journal =
                File.createTempFile(JOURNAL_FILENAME_PREFIX,
                                    null,
                                    journalDirectory);
        journal.deleteOnExit();
        FileWriter writer = new FileWriter(journal);
        writer.write(text);
        writer.close();
    }

    /**
     * Confirm that the last message in the log is a lock message, and return
     * its position in the logger.
     */
    private int assertLockMessageInLog() {
        List<String> messages = MockJournalRecoveryLog.getMessages();
        int lastMessageIndex = messages.size() - 1;
        String lastMessage = messages.get(lastMessageIndex);
        assertStringStartsWith(lastMessage, "Lock request detected:");
        return lastMessageIndex;
    }

    /**
     * Confirm that the log message following the lock message is in fact an
     * unlock message.
     */
    private void assertUnlockMessageInLog(int lockMessageIndex) {
        List<String> messages = MockJournalRecoveryLog.getMessages();
        int unlockMessageIndex = lockMessageIndex + 1;
        assertTrue(messages.size() > unlockMessageIndex);
        String unlockMessage = messages.get(unlockMessageIndex);
        assertStringStartsWith(unlockMessage, "Lock request removed");
    }

    private void assertStringStartsWith(String string, String prefix) {
        if (!string.startsWith(prefix)) {
            fail("String does not start as expected: string='" + string
                    + "', prefix='" + prefix + "'");
        }
    }

    private void processException(Throwable e) {
        if (e instanceof ServerException) {
            System.err.println("ServerException: code='"
                    + ((ServerException) e).getCode() + "', class='"
                    + e.getClass().getName() + "'");
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement element : traces) {
                System.err.println(element);
            }
            Throwable cause = e.getCause();
            if (cause != null) {
                cause.printStackTrace();
            }
            fail("Threw a ServerException");
        } else {
            e.printStackTrace();
            fail("Threw an exception");
        }
    }

    private File createTempDirectory(String name) throws IOException {
        return tmpFolder.newFolder(name);
    }

    private String getSimpleIngestString() {
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
                + "<FedoraJournal repositoryHash=\""
                + DUMMY_HASH_VALUE
                + "\" timestamp=\"2006-08-11T11:14:43.011-0400\">\n"
                + "  <JournalEntry method=\"ingest\" timestamp=\"2006-08-11T11:14:42.690-0400\" clientIpAddress=\"128.84.103.30\" loginId=\"fedoraAdmin\">\n"
                + "    <context>\n"
                + "      <password>junk</password>\n"
                + "      <noOp>false</noOp>\n"
                + "      <now>2006-08-11T11:14:42.690-0400</now>\n"
                + "      <multimap name=\"environment\">\n"
                + "        <multimapkey name=\"urn:fedora:names:fedora:2.1:environment:httpRequest:authType\">\n"
                + "          <multimapvalue>BASIC</multimapvalue>\n"
                + "        </multimapkey>\n"
                + "      </multimap>\n"
                + "      <multimap name=\"subject\"></multimap>\n"
                + "      <multimap name=\"action\"> </multimap>\n"
                + "      <multimap name=\"resource\"></multimap>\n"
                + "      <multimap name=\"recovery\"></multimap>\n"
                + "    </context>\n"
                + "    <argument name=\"serialization\" type=\"stream\">PD94</argument>\n"
                + "    <argument name=\"message\" type=\"string\">Minimal Ingest sample</argument>\n"
                + "    <argument name=\"format\" type=\"string\">"
                + FOXML1_1.uri
                + "</argument>\n"
                + "    <argument name=\"encoding\" type=\"string\">UTF-8</argument>\n"
                + "    <argument name=\"pid\" type=\"string\">new</argument>\n"
                + "  </JournalEntry>\n" + "</FedoraJournal>\n";
    }

    /**
     * Set one of these as the ingest object on the ManagementDelegate. When the
     * second ingest operation begins, a Lock Request will be created.
     */
    private final class LockAfterSecondIngest
            implements Runnable {

        public void run() {
            if (delegate.getCallCount() == 2) {
                try {
                    createLockRequest();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * This sub-class of {@link MockManagementDelegate} allows us to insert a
     * {@link Runnable} that will be executed in the middle of an ingest call.
     *
     * @author Firstname Lastname
     */
    private static class MyMockManagementDelegate
            extends MockManagementDelegate {

        private Runnable ingestOperation;

        public void setIngestOperation(Runnable ingestOperation) {
            this.ingestOperation = ingestOperation;
        }

        @Override
        public String ingest(Context context,
                             InputStream serialization,
                             String logMessage,
                             String format,
                             String encoding,
                             String pid) throws ServerException {
            String result =
                    super.ingest(context,
                                 serialization,
                                 logMessage,
                                 format,
                                 encoding,
                                 pid);

            if (ingestOperation != null) {
                ingestOperation.run();
            }

            return result;
        }

    }

}
TOP

Related Classes of org.fcrepo.server.journal.readerwriter.multifile.TestLockingFollowingJournalReader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.