Package org.fcrepo.server.journal

Source Code of org.fcrepo.server.journal.JournalReader

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

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.fcrepo.server.errors.ModuleInitializationException;
import org.fcrepo.server.errors.ServerException;
import org.fcrepo.server.journal.entry.ConsumerJournalEntry;
import org.fcrepo.server.journal.entry.JournalEntryContext;
import org.fcrepo.server.journal.helpers.DecodingBase64OutputStream;
import org.fcrepo.server.journal.helpers.JournalHelper;
import org.fcrepo.server.journal.recoverylog.JournalRecoveryLog;
import org.fcrepo.server.journal.xmlhelpers.AbstractXmlReader;
import org.fcrepo.server.journal.xmlhelpers.ContextXmlReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* The abstract base for all JournalReader classes.
* <p>
* Each child class is responsible for providing an XMLEventReader that is
* positioned at the beginning of a JournalEntry tag. This class will read the
* entry and leave the XMLEventReader positioned after the corresponding closing
* tag.
*
* @author Jim Blake
*/
public abstract class JournalReader
        extends AbstractXmlReader
        implements JournalConstants {

    private static final Logger logger =
            LoggerFactory.getLogger(JournalReader.class);

    protected final Map<String, String> parameters;

    protected final String role;

    protected final JournalRecoveryLog recoveryLog;

    protected final ServerInterface server;

    private boolean ignoreHashErrors;

    /**
     * Create an instance of the proper JournalReader child class, as determined
     * by the server parameters.
     */
    public static JournalReader getInstance(Map<String, String> parameters,
                                            String role,
                                            JournalRecoveryLog recoveryLog,
                                            ServerInterface server)
            throws ModuleInitializationException {

        try {
            Object journalReader =
                    JournalHelper
                            .createInstanceAccordingToParameter(PARAMETER_JOURNAL_READER_CLASSNAME,
                                                                new Class[] {
                                                                        Map.class,
                                                                        String.class,
                                                                        JournalRecoveryLog.class,
                                                                        ServerInterface.class},
                                                                new Object[] {
                                                                        parameters,
                                                                        role,
                                                                        recoveryLog,
                                                                        server},
                                                                parameters);
            logger.info("JournalReader is " + journalReader.toString());
            return (JournalReader) journalReader;
        } catch (JournalException e) {
            String msg = "Can't create JournalReader";
            logger.error(msg, e);
            throw new ModuleInitializationException(msg, role, e);
        }
    }

    /**
     * Concrete sub-classes must implement this constructor.
     */
    protected JournalReader(Map<String, String> parameters,
                            String role,
                            JournalRecoveryLog recoveryLog,
                            ServerInterface server)
            throws JournalException {
        this.parameters = parameters;
        this.role = role;
        this.recoveryLog = recoveryLog;
        this.server = server;
        parseParameters();
    }

    private void parseParameters() throws JournalException {
        String ignore = parameters.get(PARAMETER_IGNORE_HASH);
        if (ignore == null) {
            ignoreHashErrors = false;
        } else if (ignore.equals(VALUE_FALSE)) {
            ignoreHashErrors = false;
        } else if (ignore.equals(VALUE_TRUE)) {
            ignoreHashErrors = true;
        } else {
            throw new JournalException("'" + PARAMETER_IGNORE_HASH
                    + "' parameter must be '" + VALUE_FALSE + "'(default) or '"
                    + VALUE_TRUE + "'");
        }
    }

    /**
     * Concrete sub-classes should probably synchronize this method, since it
     * can be called either from the JournalConsumerThread or from the Server.
     */
    public abstract void shutdown() throws JournalException;

    /**
     * Concrete sub-classes should insure that their XMLEventReader is
     * positioned at the beginning of a JournalEntry, and call
     * {@link #readJournalEntry(XMLEventReader)}. It is likely that this method
     * should be synchronized also, since it could be called from
     * JournalConsumerThread when the server calls {@link #shutdown()}
     */
    public abstract ConsumerJournalEntry readJournalEntry()
            throws JournalException, XMLStreamException;

    /**
     * Compare the repository hash from the journal file with the current hash
     * obtained from the server. If they do not match, either throw an exception
     * or simply log it, depending on the parameters. This method must not be
     * called before the server has completed initialization. That's the only
     * way we can be confident that the DOManager is present, and ready to
     * create the repository has that we will compare to.
     */
    protected void checkRepositoryHash(String hash) throws JournalException {
        if (!server.hasInitialized()) {
            throw new IllegalStateException("The repository has is not available until "
                    + "the server is fully initialized.");
        }

        JournalException hashException = null;

        if (hash == null) {
            hashException =
                    new JournalException("'" + QNAME_TAG_JOURNAL
                            + "' tag must have a '"
                            + QNAME_ATTR_REPOSITORY_HASH + "' attribute.");
        } else {
            try {
                String currentHash = server.getRepositoryHash();
                if (hash.equals(currentHash)) {
                    recoveryLog.log("Validated repository hash: '" + hash
                            + "'.");
                } else {
                    hashException =
                            new JournalException("'"
                                    + QNAME_ATTR_REPOSITORY_HASH
                                    + "' attribute in '"
                                    + QNAME_TAG_JOURNAL
                                    + "' tag does not match current repository hash: '"
                                    + hash + "' vs. '" + currentHash + "'.");
                }
            } catch (ServerException e) {
                hashException = new JournalException(e);
            }
        }

        if (hashException != null) {
            if (ignoreHashErrors) {
                recoveryLog.log("WARNING: " + hashException.getMessage());
            } else {
                throw hashException;
            }
        }

    }

    /**
     * Read a JournalEntry from the journal, to produce a
     * <code>ConsumerJournalEntry</code> instance. Concrete sub-classes should
     * insure that the XMLEventReader is positioned at the beginning of a
     * JournalEntry before calling this method.
     */
    protected ConsumerJournalEntry readJournalEntry(XMLEventReader reader)
            throws JournalException, XMLStreamException {
        StartElement startTag = getJournalEntryStartTag(reader);
        String methodName =
                getRequiredAttributeValue(startTag, QNAME_ATTR_METHOD);

        JournalEntryContext context =
                new ContextXmlReader().readContext(reader);
        ConsumerJournalEntry cje =
                new ConsumerJournalEntry(methodName, context);

        readArguments(reader, cje);

        return cje;
    }

    /**
     * Get the next event and complain if it isn't a JournalEntry start tag.
     */
    private StartElement getJournalEntryStartTag(XMLEventReader reader)
            throws XMLStreamException, JournalException {
        XMLEvent event = reader.nextTag();
        if (!isStartTagEvent(event, QNAME_TAG_JOURNAL_ENTRY)) {
            throw getNotStartTagException(QNAME_TAG_JOURNAL_ENTRY, event);
        }
        return event.asStartElement();
    }

    /**
     * Read arguments and add them to the event, until we hit the end tag for
     * the event.
     */
    private void readArguments(XMLEventReader reader, ConsumerJournalEntry cje)
            throws XMLStreamException, JournalException {
        while (true) {
            XMLEvent nextTag = reader.nextTag();
            if (isStartTagEvent(nextTag, QNAME_TAG_ARGUMENT)) {
                readArgument(nextTag, reader, cje);
            } else if (isEndTagEvent(nextTag, QNAME_TAG_JOURNAL_ENTRY)) {
                return;
            } else {
                throw getNotNextMemberOrEndOfGroupException(QNAME_TAG_JOURNAL_ENTRY,
                                                            QNAME_TAG_ARGUMENT,
                                                            nextTag);
            }
        }
    }

    private void readArgument(XMLEvent nextTag,
                              XMLEventReader reader,
                              ConsumerJournalEntry journalEntry)
            throws JournalException, XMLStreamException {
        StartElement element = nextTag.asStartElement();

        String argName = getRequiredAttributeValue(element, QNAME_ATTR_NAME);
        String argType = getRequiredAttributeValue(element, QNAME_ATTR_TYPE);

        if (ARGUMENT_TYPE_NULL.equals(argType)) {
            readNullArgument(reader, journalEntry, argName);
        } else if (ARGUMENT_TYPE_STRING.equals(argType)) {
            readStringArgument(reader, journalEntry, argName);
        } else if (ARGUMENT_TYPE_STRINGARRAY.equals(argType)) {
            readStringArrayArgument(reader, journalEntry, argName);
        } else if (ARGUMENT_TYPE_INTEGER.equals(argType)) {
            readIntegerArgument(reader, journalEntry, argName);
        } else if (ARGUMENT_TYPE_BOOLEAN.equals(argType)) {
            readBooleanArgument(reader, journalEntry, argName);
        } else if (ARGUMENT_TYPE_DATE.equals(argType)) {
            readDateArgument(reader, journalEntry, argName);
        } else if (ARGUMENT_TYPE_STREAM.equals(argType)) {
            readStreamArgument(reader, journalEntry, argName);
        } else {
            throw new JournalException("Unknown argument type: name='"
                    + argName + "', type='" + argType + "'");
        }
    }

    private void readStringArgument(XMLEventReader reader,
                                    ConsumerJournalEntry journalEntry,
                                    String name) throws XMLStreamException,
            JournalException {
        String value =
                readCharactersUntilEndOfArgument(reader,
                                                 QNAME_TAG_ARGUMENT,
                                                 journalEntry.getMethodName(),
                                                 name,
                                                 ARGUMENT_TYPE_STRING);
        journalEntry.addArgument(name, value);
    }

    private void readStringArrayArgument(XMLEventReader reader,
                                         ConsumerJournalEntry journalEntry,
                                         String name)
            throws XMLStreamException, JournalException {
        List<String> values = new ArrayList<String>();
        while (true) {
            XMLEvent event = reader.nextTag();
            if (isStartTagEvent(event, QNAME_TAG_ARRAYELEMENT)) {
                values
                        .add(readCharactersUntilEndOfArgument(reader,
                                                              QNAME_TAG_ARRAYELEMENT,
                                                              journalEntry
                                                                      .getMethodName(),
                                                              name,
                                                              ARGUMENT_TYPE_STRINGARRAY));
            } else if (isEndTagEvent(event, QNAME_TAG_ARGUMENT)) {
                break;
            } else {
                throw getUnexpectedEventInArgumentException(name,
                                                            ARGUMENT_TYPE_STRINGARRAY,
                                                            journalEntry
                                                                    .getMethodName(),
                                                            event);
            }
        }
        Object[] valuesArray = values.toArray(new String[values.size()]);
        journalEntry.addArgument(name, valuesArray);
    }

    private void readIntegerArgument(XMLEventReader reader,
                                     ConsumerJournalEntry journalEntry,
                                     String name) throws XMLStreamException,
            JournalException {
        XMLEvent chars = reader.nextEvent();
        if (!chars.isCharacters()) {
            throw getUnexpectedEventInArgumentException(name,
                                                        ARGUMENT_TYPE_INTEGER,
                                                        journalEntry
                                                                .getMethodName(),
                                                        chars);
        }

        Integer integerValue = Integer.valueOf(chars.asCharacters().getData());

        XMLEvent endTag = reader.nextEvent();
        if (isEndTagEvent(endTag, QNAME_TAG_ARGUMENT)) {
            journalEntry.addArgument(name, integerValue);
        } else {
            throw getUnexpectedEventInArgumentException(name,
                                                        ARGUMENT_TYPE_INTEGER,
                                                        journalEntry
                                                                .getMethodName(),
                                                        endTag);
        }
    }

    private void readBooleanArgument(XMLEventReader reader,
                                     ConsumerJournalEntry journalEntry,
                                     String name) throws XMLStreamException,
            JournalException {
        XMLEvent chars = reader.nextEvent();
        if (!chars.isCharacters()) {
            throw getUnexpectedEventInArgumentException(name,
                                                        ARGUMENT_TYPE_BOOLEAN,
                                                        journalEntry
                                                                .getMethodName(),
                                                        chars);
        }

        Boolean booleanValue = Boolean.valueOf(chars.asCharacters().getData());

        XMLEvent endTag = reader.nextEvent();
        if (isEndTagEvent(endTag, QNAME_TAG_ARGUMENT)) {
            journalEntry.addArgument(name, booleanValue);
        } else {
            throw getUnexpectedEventInArgumentException(name,
                                                        ARGUMENT_TYPE_BOOLEAN,
                                                        journalEntry
                                                                .getMethodName(),
                                                        endTag);
        }
    }

    private void readDateArgument(XMLEventReader reader,
                                  ConsumerJournalEntry journalEntry,
                                  String name) throws XMLStreamException,
            JournalException {
        XMLEvent chars = reader.nextEvent();
        if (!chars.isCharacters()) {
            throw getUnexpectedEventInArgumentException(name,
                                                        ARGUMENT_TYPE_BOOLEAN,
                                                        journalEntry
                                                                .getMethodName(),
                                                        chars);
        }

        Date dateValue =
                JournalHelper.parseDate(chars.asCharacters().getData());

        XMLEvent endTag = reader.nextEvent();
        if (isEndTagEvent(endTag, QNAME_TAG_ARGUMENT)) {
            journalEntry.addArgument(name, dateValue);
        } else {
            throw getUnexpectedEventInArgumentException(name,
                                                        ARGUMENT_TYPE_DATE,
                                                        journalEntry
                                                                .getMethodName(),
                                                        endTag);
        }
    }

    /**
     * An InputStream argument appears as a Base64-encoded String. It must be
     * decoded and written to a temp file, so it can be presented to the
     * management method as an InputStream again.
     */
    private void readStreamArgument(XMLEventReader reader,
                                    ConsumerJournalEntry journalEntry,
                                    String name) throws XMLStreamException,
            JournalException {
        try {
            File tempFile = JournalHelper.createTempFile();
            DecodingBase64OutputStream decoder =
                    new DecodingBase64OutputStream(new FileOutputStream(tempFile));

            while (true) {
                XMLEvent event = reader.nextEvent();
                if (event.isCharacters()) {
                    decoder.write(event.asCharacters().getData());
                } else if (isEndTagEvent(event, QNAME_TAG_ARGUMENT)) {
                    break;
                } else {
                    throw getUnexpectedEventInArgumentException(name,
                                                                ARGUMENT_TYPE_STREAM,
                                                                journalEntry
                                                                        .getMethodName(),
                                                                event);
                }
            }
            decoder.close();
            journalEntry.addArgument(name, tempFile);
        } catch (IOException e) {
            throw new JournalException("failed to write stream argument to temp file",
                                       e);
        }
    }

    private void readNullArgument(XMLEventReader reader,
                                  ConsumerJournalEntry journalEntry,
                                  String name) throws XMLStreamException,
            JournalException {
        XMLEvent endTag = reader.nextTag();
        if (!isEndTagEvent(endTag, QNAME_TAG_ARGUMENT)) {
            throw getUnexpectedEventInArgumentException(name,
                                                        ARGUMENT_TYPE_NULL,
                                                        journalEntry
                                                                .getMethodName(),
                                                        endTag);
        }
    }

    /**
     * Loop through a series of character events, accumulating the data into a
     * String. The character events should be terminated by an EndTagEvent with
     * the expected tag name.
     */
    private String readCharactersUntilEndOfArgument(XMLEventReader reader,
                                                    QName tagName,
                                                    String methodName,
                                                    String argumentName,
                                                    String argumentType)
            throws XMLStreamException, JournalException {
        StringBuffer stringValue = new StringBuffer();
        while (true) {
            XMLEvent event = reader.nextEvent();
            if (event.isCharacters()) {
                stringValue.append(event.asCharacters().getData());
            } else if (isEndTagEvent(event, tagName)) {
                break;
            } else {
                throw getUnexpectedEventInArgumentException(argumentName,
                                                            argumentType,
                                                            methodName,
                                                            event);
            }
        }
        return stringValue.toString();
    }

    /**
     * If we encounter an unexpected event when reading the a method argument,
     * create an exception with all of the pertinent information.
     */
    private JournalException getUnexpectedEventInArgumentException(String name,
                                                                   String argumentType,
                                                                   String methodName,
                                                                   XMLEvent event) {
        return new JournalException("Unexpected event while processing '"
                + name + "' argument (type = '" + argumentType + "') for '"
                + methodName + "' method call: event is '" + event + "'");
    }

}
TOP

Related Classes of org.fcrepo.server.journal.JournalReader

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.