All Downloads are FREE. Search and download functionalities are using the official Maven repository.

test.junit.fedora.server.journal.TestJournalRoundTrip Maven / Gradle / Ivy


package fedora.server.journal;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import junit.framework.JUnit4TestAdapter;

import org.junit.Before;
import org.junit.Test;

import fedora.common.Constants;
import fedora.common.rdf.RDFName;

import fedora.server.Context;
import fedora.server.errors.InvalidStateException;
import fedora.server.errors.ModuleInitializationException;
import fedora.server.errors.ModuleShutdownException;
import fedora.server.errors.ServerException;
import fedora.server.journal.entry.JournalEntryContext;
import fedora.server.management.Management;
import fedora.server.management.MockManagementDelegate;
import fedora.server.management.MockManagementDelegate.Call;
import fedora.server.storage.types.DSBinding;
import fedora.server.storage.types.DSBindingMap;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;

/**
 * Check every management method to be sure:
 * 
    *
  • that we know whether it is journalled or not
  • *
  • that we know what items are stored in the context for recovery
  • *
  • that a journalled method is played back the same as it was recorded
  • *
  • that a journalled method WILL NOT be accepted from an outside source by * a JournalConsumer
  • *
  • that a non-journalled method WILL be accepted from an outside source by * a JournalConsumer
  • *
* * @author Jim Blake */ public class TestJournalRoundTrip { private static final String METHOD_GET_TEMP_STREAM = "getTempStream"; private static final String THE_ROLE = "theRole"; private static final String THE_SERVER_HASH = "theServerHash"; public static junit.framework.Test suite() { return new JUnit4TestAdapter(TestJournalRoundTrip.class); } /** * Defines the names of the classes used for reading, writing, and logging. */ private Map journalParameters; /** * Context items that will be provided to the JournalCreator. */ private JournalEntryContext leadingContext; /** * Attributes that we expect to see added to the {@link #leadingContext} * when the management method is called. */ private Map contextAdditions; /** * Context items as expected after the call. */ private JournalEntryContext expectedContext; /** * The journal module of the "leader" server. */ private JournalCreator creator; /** * Records the calls that are submitted to the Management interface by the * JournalCreator. */ private MockManagementDelegate leadingDelegate; /** * The journal module of the "follower" server. */ private JournalConsumer consumer; /** * Records the calls that are submitted to the Management interface by the * JournalConsumer. */ MockManagementDelegate followingDelegate; private Call expectedCall; private DSBindingMap bindingMap; /* * ------------------------------------------------------------------------ * Setup * ------------------------------------------------------------------------ */ /** * For each test, set the parameters that will be required by the * {@link #creator} and the {@link #consumer}. */ @Before public void initializeJournalParameters() { journalParameters = new HashMap(); journalParameters .put(JournalConstants.PARAMETER_JOURNAL_WRITER_CLASSNAME, MockJournalWriter.class.getName()); journalParameters .put(JournalConstants.PARAMETER_JOURNAL_READER_CLASSNAME, MockJournalReader.class.getName()); journalParameters .put(JournalConstants.PARAMETER_JOURNAL_RECOVERY_LOG_CLASSNAME, MockJournalRecoveryLog.class.getName()); } @Before public void initializeContextObjects() { leadingContext = new JournalEntryContext(); expectedContext = null; contextAdditions = new HashMap(); } @Before public void initializeExpectedCall() { // Every test should call buildExpectedCall(). expectedCall = null; } @Before public void initializeBindingMap() { bindingMap = new DSBindingMap(); bindingMap.dsBindMapID = "bindMapID"; bindingMap.dsBindMapLabel = "bindMapLabel"; bindingMap.dsBindMechanismPID = "bindMechPID"; bindingMap.dsBindings = new DSBinding[0]; bindingMap.state = "bindState"; } /* * ------------------------------------------------------------------------ * The tests * ------------------------------------------------------------------------ */ @Test public void addDatastream() throws ServerException { expectInContext(Constants.RECOVERY.DATASTREAM_ID, "theDsId"); testJournalledMethod(JournalConstants.METHOD_ADD_DATASTREAM, leadingContext, "thePid", "theDsId", new String[0], "theDsLabel", false, "theMIMEType", "theFormatURI", "theLocation", "theControlGroup", "theDsState", "theChecksumType", "theChecksum", "theLogMessage"); } @Test public void addDisseminator() throws ServerException { expectInContext(Constants.RECOVERY.DISSEMINATOR_ID, "dissID"); testJournalledMethod(JournalConstants.METHOD_ADD_DISSEMINATOR, leadingContext, "thePid", "theBdefPID", "aBmechPID", "dissLabel", bindingMap, "dissState", "theLogMessage"); } @Test public void adminPing() throws ServerException { testNonJournalledMethod("adminPing", leadingContext); } @Test public void compareDatastreamChecksum() throws ServerException { testNonJournalledMethod("compareDatastreamChecksum", leadingContext, "thePid", "theDsId", new Date(12345L)); } @Test public void exportObject() throws ServerException { testNonJournalledMethod("exportObject", leadingContext, "PID", "format", "SomeExportContext", "encoding"); } @Test public void getDatastream() throws ServerException { testNonJournalledMethod("getDatastream", leadingContext, "PID", "aDatastreamID", new Date()); } @Test public void getDatastreamHistory() throws ServerException { testNonJournalledMethod("getDatastreamHistory", leadingContext, "PID", "anotherDatastreamID"); } @Test public void getDatastreams() throws ServerException { testNonJournalledMethod("getDatastreams", leadingContext, "sonOfPID", new Date(111111L), "someStateString"); } @Test public void getDisseminator() throws ServerException { testNonJournalledMethod("getDisseminator", leadingContext, "anotherPID", "dissID", new Date(111111L)); } @Test public void getDisseminators() throws ServerException { testNonJournalledMethod("getDisseminators", leadingContext, "sonOfPID", new Date(222111L), "someDissState"); } @Test public void getDisseminatorHistory() throws ServerException { testNonJournalledMethod("getDisseminatorHistory", leadingContext, "sonOfPID", "dissID"); } @Test public void getNextPID() throws ServerException { expectInContext(Constants.RECOVERY.PID_LIST, new String[] { "sillyPID_0", "sillyPID_1", "sillyPID_2", "sillyPID_3", "sillyPID_4"}); testJournalledMethod(JournalConstants.METHOD_GET_NEXT_PID, leadingContext, 5, "myFavoriteNamespace"); } @Test public void getObjectProperties() throws ServerException { testNonJournalledMethod("getObjectProperties", leadingContext, "yaPID"); } @Test public void getObjectXML() throws ServerException { testNonJournalledMethod("getObjectXML", leadingContext, "myPID", "encodingScheme"); } /** * This one will always be special, in that it doesn't use a context as its * first argument. If it were a Journalled method, that would be a problem. */ @Test public void getTempStream() throws ServerException { testNonJournalledMethod(METHOD_GET_TEMP_STREAM, "streamID"); } @Test public void ingestObject() throws ServerException { expectInContext(Constants.RECOVERY.PID, "IngestObject:1"); testJournalledMethod(JournalConstants.METHOD_INGEST_OBJECT, leadingContext, new ByteArrayInputStream(new byte[0]), "theLogMessage", "aFormat", "someEncoding", true); } @Test public void modifyDatastreamByReference() throws ServerException { testJournalledMethod(JournalConstants.METHOD_MODIFY_DATASTREAM_BY_REFERENCE, leadingContext, "myPid", "datastreamIdentifier", new String[] {"altID"}, "datastreamLabel", "mime/type", "formatUri", "dsLocation", "checksumType", "checksum", "logMessage", false); } @Test public void modifyDatastreamByValue() throws ServerException { testJournalledMethod(JournalConstants.METHOD_MODIFY_DATASTREAM_BY_VALUE, leadingContext, "myPid", "datastreamIdentifier", new String[] {"altID"}, "datastreamLabel", "mime/type", "formatUri", new ByteArrayInputStream(new byte[0]), "checksumType", "checksum", "logMessage", false); } @Test public void modifyObject() throws ServerException { testJournalledMethod(JournalConstants.METHOD_MODIFY_OBJECT, leadingContext, "myPid", "state", "objectLabel", "owner", "logMessage"); } @Test public void modifyDisseminator() throws ServerException { testJournalledMethod(JournalConstants.METHOD_MODIFY_DISSEMINATOR, leadingContext, "myPid", "disseminatorID", "bMechPID", "disseminatorLabel", bindingMap, "state", "logMessage", false); } @Test public void purgeDatastream() throws ServerException { testJournalledMethod(JournalConstants.METHOD_PURGE_DATASTREAM, leadingContext, "myPid", "dsID", new Date(123), new Date(456), "logMessage", true); } @Test public void purgeDisseminator() throws ServerException { testJournalledMethod(JournalConstants.METHOD_PURGE_DISSEMINATOR, leadingContext, "myPid", "dissID", new Date(123), "logMessage"); } @Test public void purgeObject() throws ServerException { testJournalledMethod(JournalConstants.METHOD_PURGE_OBJECT, leadingContext, "aPID", "PurgeLogMessage", true); } @Test public void putTempStream() throws ServerException { expectInContext(Constants.RECOVERY.UPLOAD_ID, "tempStreamId"); testJournalledMethod(JournalConstants.METHOD_PUT_TEMP_STREAM, leadingContext, new ByteArrayInputStream(new byte[0])); } @Test public void setDatastreamState() throws ServerException { testJournalledMethod(JournalConstants.METHOD_SET_DATASTREAM_STATE, leadingContext, "pid", "dsID", "dsState", "dsLogMessage"); } @Test public void setDatastreamVersionable() throws ServerException { testJournalledMethod(JournalConstants.METHOD_SET_DATASTREAM_VERSIONABLE, leadingContext, "lastPID", "lastDsID", true, "the Log!"); } @Test public void setDisseminatorState() throws ServerException { testJournalledMethod(JournalConstants.METHOD_SET_DISSEMINATOR_STATE, leadingContext, "yaPID", "yaDsID", "newState", "the Message"); } /* * ------------------------------------------------------------------------ * Helper methods. * ------------------------------------------------------------------------ */ /** * What additional "recovery attributes" should we expect the leading * delegate to set into the context? */ private void expectInContext(RDFName key, Object value) { contextAdditions.put(key, value); } /** *

* Test a Journalled method. *

*

* Call the selected method on the JournalCreator. Then tell the * JournalConsumer to process the journal. Compare the calls received by * both Management delegates to the call we expected them to see. *

*

* Calling the method directly on the JournalConsumer should produce an * exception. *

* * @throws ModuleInitializationException * @throws ModuleShutdownException */ private void testJournalledMethod(String methodName, Object... arguments) throws ServerException { buildExpectedCall(methodName, arguments); setupLeader(); executeManagmentMethod(creator, methodName, arguments); closeLeader(); setupFollower(); letFollowerCatchUp(); assertExpectedCall("leading", leadingDelegate); assertExpectedCall("following", followingDelegate); try { executeManagmentMethod(consumer, methodName, arguments); fail("expected an InvalidStateException"); } catch (InvalidStateException e) { // That's the one we expected. } } /** *

* Test a non-Journalled method. *

*

* Call the selected method on the JournalCreator. Check that the Journal is * empty. Compare the call received by the Management delegates to the call * we expected it to see. *

*

* Call the selected method on the JournalConsumer. Again, compare the call * to the one that we expected. *

*/ private void testNonJournalledMethod(String methodName, Object... arguments) throws ServerException { buildExpectedCall(methodName, arguments); setupLeader(); executeManagmentMethod(creator, methodName, arguments); closeLeader(); assertEmptyJournal(); assertExpectedCall("leading", leadingDelegate); setupFollower(); letFollowerCatchUp(); executeManagmentMethod(consumer, methodName, arguments); assertExpectedCall("following", followingDelegate); } /** *

* The expected call includes the expected context, as well as the supplied * arguments. *

*

* Note that "getTempStream" is the only Management call that doesn't use * "context" as its first argument. *

*/ private void buildExpectedCall(String methodName, Object[] arguments) { loadExpectedContext(); if (arguments[0] == leadingContext) { arguments[0] = expectedContext; } expectedCall = new Call(methodName, arguments); } /** * The {@link #expectedContext} should be like the {@link #leadingContext}, * with the addition of anything we expect the management method to add. */ private void loadExpectedContext() { expectedContext = new JournalEntryContext(leadingContext); for (Map.Entry entry : contextAdditions.entrySet()) { try { expectedContext.getRecoveryAttributes().set(entry.getKey().uri, entry.getValue()); } catch (Exception e) { fail("Stupid design of MultiValueMap"); } } } private void setupLeader() throws ModuleInitializationException { leadingDelegate = new MockManagementDelegate(); MockServerForJournalTesting leadingServer = new MockServerForJournalTesting(leadingDelegate, THE_SERVER_HASH); creator = new JournalCreator(journalParameters, THE_ROLE, leadingServer); creator.setManagementDelegate(leadingDelegate); } /** *

* Note that "getTempStream" is the only Management call that doesn't use * "context" as its first argument. *

*/ private void executeManagmentMethod(Management management, String methodName, Object[] arguments) throws ServerException { Class[] argTypes = new Class[arguments.length]; for (int i = 0; i < arguments.length; i++) { argTypes[i] = getArgType(arguments[i]); } try { Method method = Management.class.getDeclaredMethod(methodName, argTypes); method.invoke(management, arguments); } catch (SecurityException e) { e.printStackTrace(); fail("Failed to invoke the method: " + e); } catch (IllegalArgumentException e) { e.printStackTrace(); fail("Failed to invoke the method: " + e); } catch (NoSuchMethodException e) { e.printStackTrace(); fail("Failed to invoke the method: " + e); } catch (IllegalAccessException e) { e.printStackTrace(); fail("Failed to invoke the method: " + e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof ServerException) { throw (ServerException) cause; } else { e.printStackTrace(); fail("Failed to invoke the method: " + e); } } } /** * Figure the class of each argument to the call, assuming that we always * use boolean or int rather than * {@link Bookean} or {@link Integer}. Also, use {@link Context} and * {@link InputStream} rather than one of their subclasses. */ private Class getArgType(Object argument) { if (argument == null) { fail("Can't run unit test with null arguments."); } Class argType = argument.getClass(); if (argType.equals(Integer.class)) { argType = Integer.TYPE; } if (argType.equals(Boolean.class)) { argType = Boolean.TYPE; } if (Context.class.isAssignableFrom(argType)) { argType = Context.class; } if (InputStream.class.isAssignableFrom(argType)) { argType = InputStream.class; } return argType; } private void closeLeader() throws ModuleShutdownException { // Tell the leader to close the "file". creator.shutdown(); // Transfer the journal contents to the reader. MockJournalReader.setBuffer(MockJournalWriter.getBuffer()); } public void setupFollower() throws ModuleInitializationException { followingDelegate = new MockManagementDelegate(); MockServerForJournalTesting followingServer = new MockServerForJournalTesting(followingDelegate, THE_SERVER_HASH); consumer = new JournalConsumer(journalParameters, THE_ROLE, followingServer); } private void letFollowerCatchUp() throws ModuleShutdownException { // Start the follower. consumer.setManagementDelegate(followingDelegate); // Wait for it to catch up. waitForConsumerThread(); // Shut it down. consumer.shutdown(); } /** * Compare the expected call with the call that the delegate actually * recorded. */ private void assertExpectedCall(String label, MockManagementDelegate delegate) { if (delegate.getCallCount() != 1) { fail("Wrong number of " + label + " calls: expected 1, actual " + delegate.getCallCount() + ". Calls are as follows:\n" + delegate.getCalls()); } assertEquals(label + " calls", expectedCall, delegate.getCalls().get(0)); } private void assertEmptyJournal() { assertEquals("non-empty journal", 0, MockJournalWriter.getBuffer() .length()); } /** * Let me know when the follower has caught up. */ private void waitForConsumerThread() { try { // Shouldn't there be a better way? Thread.sleep(500); } catch (InterruptedException e) { // Won't happen; wouldn't care. } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy