/*
* Copyright 2012 Splunk, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"): you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.splunk;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
/**
* The {@code AtomEntry} class represents an Atom {@code <entry>} element.
*/
public class AtomEntry extends AtomObject {
/** The value of the Atom entry's {@code <published>} element. */
public String published;
/** The value of the Atom entry's {@code <content>} element. */
public Record content;
/**
* Creates a new {@code AtomEntry} instance.
*
* @return A new {@code AtomEntry} instance.
*/
static AtomEntry create() {
return new AtomEntry();
}
/**
* Creates a new {@code AtomEntry} instance based on a given stream.
* A few endpoints, such as {@code search/jobs/{sid}},
* return an Atom {@code <entry>} element as the root of the response.
*
* @param input The input stream.
* @return An {@code AtomEntry} instance representing the parsed stream.
*/
public static AtomEntry parseStream(InputStream input) {
XMLStreamReader reader = createReader(input);
AtomEntry result = AtomEntry.parse(reader);
try {
reader.close();
}
catch (XMLStreamException e) {
throw new RuntimeException(e.getMessage(), e);
}
return result;
}
/**
* Creates a new {@code AtomEntry} instance based on a given XML reader.
*
* @param reader The XML reader.
* @return An {@code AtomEntry} instance representing the parsed XML.
*/
static AtomEntry parse(XMLStreamReader reader) {
AtomEntry entry = AtomEntry.create();
entry.load(reader, "entry");
return entry;
}
/**
* Initializes the current instance using the given XML reader.
*
* @param reader The XML reader.
*/
@Override void init(XMLStreamReader reader) {
assert reader.isStartElement();
String name = reader.getLocalName();
if (name.equals("published")) {
this.published = parseText(reader);
}
else if (name.equals("content")) {
this.content = parseContent(reader);
}
else {
super.init(reader);
}
}
/**
* Parses the {@code <content>} element of an Atom entry.
*
* @param reader The XML reader.
* @return A {@code Record} object containing the parsed values.
*/
private Record parseContent(XMLStreamReader reader) {
assert isStartElement(reader, "content");
scan(reader);
// The content element should contain a single <dict> element
if (!isStartElement(reader, "dict"))
syntaxError(reader);
content = parseDict(reader);
if (!isEndElement(reader, "content"))
syntaxError(reader);
scan(reader); // Consume </content>
return content;
}
/**
* Parses a {@code <dict>} content element and returns a {@code Record}
* object containing the parsed values.
*
* @param reader The {@code <dict>} element to parse.
* @return A {@code Record} object containing the parsed values.
*/
private Record parseDict(XMLStreamReader reader) {
assert isStartElement(reader, "dict");
Record result = new Record();
scan(reader);
while (isStartElement(reader, "key")) {
String key = reader.getAttributeValue(null, "name");
Object value = parseValue(reader);
// Null values, the result of empty elements, are parsed as though
// they don't exist, making it easier for the client framework to
// supply more meaningful default values.
if (value != null) result.put(key, value);
}
if (!isEndElement(reader, "dict"))
syntaxError(reader);
scan(reader); // Consume </dict>
return result;
}
/**
* Parses a {@code <list>} element and returns a {@code List} object
* containing the parsed values.
*
* @param reader The XML reader.
* @return A {@code List} object containing the parsed values.
*/
private List parseList(XMLStreamReader reader) {
assert isStartElement(reader, "list");
List result = new ArrayList();
scan(reader);
while (isStartElement(reader, "item")) {
Object value = parseValue(reader);
result.add(value);
}
if (!isEndElement(reader, "list"))
syntaxError(reader);
scan(reader); // Consume </list>
return result;
}
// Parses either a dict or list structure.
private Object parseStructure(XMLStreamReader reader) {
String name = reader.getLocalName();
if (name.equals("dict"))
return parseDict(reader);
if (name.equals("list"))
return parseList(reader);
syntaxError(reader);
return null; // Unreached
}
/**
* Parses the value contained by the element at the current cursor position
* of the given reader.
* <p>
* <b>Note:</b> This function takes the parent element as its starting point
* so that it can correctly match the end element. The function takes the
* start element and its corresponding end element, then returns the
* contained value. The cursor is then located at the next element to be
* parsed.
*
* @param reader The XML reader to parse.
* @return An object containing the parsed values. If the source was a text
* value, the object is a {@code String}. If the source was a {@code <dict>}
* element, the object is a {@code Record}. If the source was a
* {@code <list>} element, the object is a {@code List} object.
*/
Object parseValue(XMLStreamReader reader) {
assert reader.isStartElement();
String name = reader.getLocalName();
scan(reader);
Object value;
switch (reader.getEventType()) {
case XMLStreamConstants.CHARACTERS:
value = reader.getText();
scan(reader); // Advance cursor
break;
case XMLStreamConstants.START_ELEMENT:
value = parseStructure(reader);
break;
case XMLStreamConstants.END_ELEMENT:
value = null; // Empty element
break;
default:
value = null;
syntaxError(reader);
}
if (!isEndElement(reader, name))
syntaxError(reader);
scan(reader); // Consume end element
return value;
}
}