/* 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 fedora.server.journal.xmlhelpers; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.XMLEvent; import fedora.server.MultiValueMap; import fedora.server.journal.JournalException; import fedora.server.journal.entry.JournalEntryContext; import fedora.server.journal.helpers.JournalHelper; import fedora.server.journal.helpers.PasswordCipher; /** * Reads a Context tag from the journal file, and assembles a * JournalEntryContext from it. * * @author Jim Blake */ public class ContextXmlReader extends AbstractXmlReader { private String passwordType; /** * Read the context tax and populate a JournalEntryContext object. */ public JournalEntryContext readContext(XMLEventReader reader) throws JournalException, XMLStreamException { JournalEntryContext context = new JournalEntryContext(); XMLEvent event = reader.nextTag(); if (!isStartTagEvent(event, QNAME_TAG_CONTEXT)) { throw getNotStartTagException(QNAME_TAG_CONTEXT, event); } context.setPassword(readContextPassword(reader)); context.setNoOp(readContextNoOp(reader)); context.setNow(readContextNow(reader)); context .setEnvironmentAttributes(readMultiMap(reader, CONTEXT_MAPNAME_ENVIRONMENT)); context.setSubjectAttributes(readMultiMap(reader, CONTEXT_MAPNAME_SUBJECT)); context .setActionAttributes(readMultiMap(reader, CONTEXT_MAPNAME_ACTION)); context.setResourceAttributes(readMultiMap(reader, CONTEXT_MAPNAME_RESOURCE)); context.setRecoveryAttributes(readMultiMap(reader, CONTEXT_MAPNAME_RECOVERY)); event = reader.nextTag(); if (!isEndTagEvent(event, QNAME_TAG_CONTEXT)) { throw getNotEndTagException(QNAME_TAG_CONTEXT, event); } decipherPassword(context); return context; } /** * Read the context password from XML. Note: While doing this, fetch the * password type, and store it to use when deciphering the password. Not the * cleanest structure, perhaps, but it will serve for now. */ private String readContextPassword(XMLEventReader reader) throws JournalException, XMLStreamException { XMLEvent startTag = readStartTag(reader, QNAME_TAG_PASSWORD); passwordType = getOptionalAttributeValue(startTag.asStartElement(), QNAME_ATTR_PASSWORD_TYPE); return readCharactersUntilEndTag(reader, QNAME_TAG_PASSWORD); } /** * Read the context no-op flag from XML. */ private boolean readContextNoOp(XMLEventReader reader) throws XMLStreamException, JournalException { readStartTag(reader, QNAME_TAG_NOOP); String value = readCharactersUntilEndTag(reader, QNAME_TAG_NOOP); return Boolean.valueOf(value).booleanValue(); } /** * Read the context date from XML. */ private Date readContextNow(XMLEventReader reader) throws XMLStreamException, JournalException { readStartTag(reader, QNAME_TAG_NOW); String value = readCharactersUntilEndTag(reader, QNAME_TAG_NOW); return JournalHelper.parseDate(value); } /** * Read a multi-map, with its nested tags. */ private MultiValueMap readMultiMap(XMLEventReader reader, String mapName) throws JournalException, XMLStreamException { MultiValueMap map = new MultiValueMap(); // must start with a multi-map tag XMLEvent event = reader.nextTag(); if (!isStartTagEvent(event, QNAME_TAG_MULTI_VALUE_MAP)) { throw getNotStartTagException(QNAME_TAG_MULTI_VALUE_MAP, event); } // the map name must match the expected name String value = getRequiredAttributeValue(event.asStartElement(), QNAME_ATTR_NAME); if (!mapName.equals(value)) { throw new JournalException("Expecting a '" + mapName + "' multi-map, but found a '" + value + "' multi-map instead"); } // populate the map readMultiMapKeys(reader, map); return map; } /** * Read through the keys of the multi-map, adding to the map as we go. */ private void readMultiMapKeys(XMLEventReader reader, MultiValueMap map) throws XMLStreamException, JournalException { while (true) { XMLEvent event2 = reader.nextTag(); if (isStartTagEvent(event2, QNAME_TAG_MULTI_VALUE_MAP_KEY)) { // if we find a key tag, get the name String key = getRequiredAttributeValue(event2.asStartElement(), QNAME_ATTR_NAME); // read as many values as we find. String[] values = readMultiMapValuesForKey(reader); // store in the map storeInMultiMap(map, key, values); } else if (isEndTagEvent(event2, QNAME_TAG_MULTI_VALUE_MAP)) { break; } else { throw getNotNextMemberOrEndOfGroupException(QNAME_TAG_MULTI_VALUE_MAP, QNAME_TAG_MULTI_VALUE_MAP_KEY, event2); } } } /** * Read the list of values for one key of the multi-map. */ private String[] readMultiMapValuesForKey(XMLEventReader reader) throws XMLStreamException, JournalException { List<String> values = new ArrayList<String>(); while (true) { XMLEvent event = reader.nextTag(); if (isStartTagEvent(event, QNAME_TAG_MULTI_VALUE_MAP_VALUE)) { values .add(readCharactersUntilEndTag(reader, QNAME_TAG_MULTI_VALUE_MAP_VALUE)); } else if (isEndTagEvent(event, QNAME_TAG_MULTI_VALUE_MAP_KEY)) { return values.toArray(new String[values.size()]); } else { throw getNotNextMemberOrEndOfGroupException(QNAME_TAG_MULTI_VALUE_MAP_KEY, QNAME_TAG_MULTI_VALUE_MAP_VALUE, event); } } } /** * This method is just to guard against the totally bogus Exception * declaration in MultiValueMap.set() */ private void storeInMultiMap(MultiValueMap map, String key, String[] values) throws JournalException { try { map.set(key, values); } catch (Exception e) { // totally bogus Exception here. throw new JournalException(e); } } /** * The password as read was not correct. It needs to be deciphered. */ private void decipherPassword(JournalEntryContext context) { String key = JournalHelper.formatDate(context.now()); String passwordCipher = context.getPassword(); String clearPassword = PasswordCipher.decipher(key, passwordCipher, passwordType); context.setPassword(clearPassword); } }