com.microsoft.windowsazure.services.table.client.AtomPubParser Maven / Gradle / Ivy
Show all versions of microsoft-windowsazure-api Show documentation
/**
* Copyright Microsoft Corporation
*
* 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.microsoft.windowsazure.services.table.client;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map.Entry;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import org.apache.commons.lang3.StringEscapeUtils;
import com.microsoft.windowsazure.services.core.storage.Constants;
import com.microsoft.windowsazure.services.core.storage.OperationContext;
import com.microsoft.windowsazure.services.core.storage.StorageException;
import com.microsoft.windowsazure.services.core.storage.utils.Utility;
/**
* Reserved for internal use. A class used to read and write Table entities in OData AtomPub format requests and
* responses.
*
* For more information about OData, see the Open Data Protocol website. For more
* information about the AtomPub format used in OData, see OData Protocol Atom Format.
*/
class AtomPubParser {
/**
* Reserved for internal use. A static factory method to construct an XMLStreamWriter
instance based on
* the specified OutputStream
.
*
* @param outStream
* The OutputStream
instance to create an XMLStreamWriter
on.
* @return
* An XMLStreamWriter
instance based on the specified OutputStream
.
* @throws XMLStreamException
* if an error occurs while creating the stream.
*/
protected static XMLStreamWriter generateTableWriter(final OutputStream outStream) throws XMLStreamException {
final XMLOutputFactory xmlOutFactoryInst = XMLOutputFactory.newInstance();
return xmlOutFactoryInst.createXMLStreamWriter(outStream, "UTF-8");
}
/**
* Reserved for internal use. Parses the operation response as an entity. Parses the result returned in the
* specified stream in AtomPub format into a {@link TableResult} containing an entity of the specified class type
* projected using the specified resolver.
*
* @param xmlr
* An XMLStreamReader
on the input stream.
* @param clazzType
* The class type T
implementing {@link TableEntity} for the entity returned. Set to
* null
to ignore the returned entity and copy only response properties into the
* {@link TableResult} object.
* @param resolver
* An {@link EntityResolver} instance to project the entity into an instance of type R
. Set
* to null
to return the entity as an instance of the class type T
.
* @param opContext
* An {@link OperationContext} object used to track the execution of the operation.
* @return
* A {@link TableResult} containing the parsed entity result of the operation.
*
* @throws XMLStreamException
* if an error occurs while accessing the stream.
* @throws ParseException
* if an error occurs while parsing the stream.
* @throws InstantiationException
* if an error occurs while constructing the result.
* @throws IllegalAccessException
* if an error occurs in reflection while parsing the result.
* @throws StorageException
* if a storage service error occurs.
*/
protected static TableResult parseEntity(final XMLStreamReader xmlr,
final Class clazzType, final EntityResolver resolver, final OperationContext opContext)
throws XMLStreamException, ParseException, InstantiationException, IllegalAccessException, StorageException {
int eventType = xmlr.getEventType();
final TableResult res = new TableResult();
xmlr.require(XMLStreamConstants.START_ELEMENT, null, ODataConstants.ENTRY);
res.setEtag(StringEscapeUtils.unescapeHtml4(xmlr.getAttributeValue(ODataConstants.DATA_SERVICES_METADATA_NS,
ODataConstants.ETAG)));
while (xmlr.hasNext()) {
eventType = xmlr.next();
if (eventType == XMLStreamConstants.CHARACTERS) {
xmlr.getText();
continue;
}
final String name = xmlr.getName().toString();
if (eventType == XMLStreamConstants.START_ELEMENT) {
if (name.equals(ODataConstants.BRACKETED_ATOM_NS + ODataConstants.ID)) {
res.setId(Utility.readElementFromXMLReader(xmlr, ODataConstants.ID));
}
else if (name.equals(ODataConstants.BRACKETED_DATA_SERVICES_METADATA_NS + ODataConstants.PROPERTIES)) {
// Do read properties
if (resolver == null && clazzType == null) {
return res;
}
else {
res.setProperties(readProperties(xmlr, opContext));
break;
}
}
}
}
// Move to end Content
eventType = xmlr.next();
if (eventType == XMLStreamConstants.CHARACTERS) {
eventType = xmlr.next();
}
xmlr.require(XMLStreamConstants.END_ELEMENT, null, ODataConstants.CONTENT);
eventType = xmlr.next();
if (eventType == XMLStreamConstants.CHARACTERS) {
eventType = xmlr.next();
}
xmlr.require(XMLStreamConstants.END_ELEMENT, null, ODataConstants.ENTRY);
String rowKey = null;
String partitionKey = null;
Date timestamp = null;
// Remove core properties from map and set individually
EntityProperty tempProp = res.getProperties().get(TableConstants.PARTITION_KEY);
if (tempProp != null) {
res.getProperties().remove(TableConstants.PARTITION_KEY);
partitionKey = tempProp.getValueAsString();
}
tempProp = res.getProperties().get(TableConstants.ROW_KEY);
if (tempProp != null) {
res.getProperties().remove(TableConstants.ROW_KEY);
rowKey = tempProp.getValueAsString();
}
tempProp = res.getProperties().get(TableConstants.TIMESTAMP);
if (tempProp != null) {
res.getProperties().remove(TableConstants.TIMESTAMP);
timestamp = tempProp.getValueAsDate();
}
if (resolver != null) {
// Call resolver
res.setResult(resolver.resolve(partitionKey, rowKey, timestamp, res.getProperties(), res.getEtag()));
}
else if (clazzType != null) {
// Generate new entity and return
final T entity = clazzType.newInstance();
entity.setEtag(res.getEtag());
entity.setPartitionKey(partitionKey);
entity.setRowKey(rowKey);
entity.setTimestamp(timestamp);
entity.readEntity(res.getProperties(), opContext);
res.setResult(entity);
}
return res;
}
/**
* Reserved for internal use. Parses the operation response as a collection of entities. Reads entity data from the
* specified input stream using the specified class type and optionally projects each entity result with the
* specified resolver into an {@link ODataPayload} containing a collection of {@link TableResult} objects. .
*
* @param inStream
* The InputStream
to read the data to parse from.
* @param clazzType
* The class type T
implementing {@link TableEntity} for the entities returned. Set to
* null
to ignore the returned entities and copy only response properties into the
* {@link TableResult} objects.
* @param resolver
* An {@link EntityResolver} instance to project the entities into instances of type R
. Set
* to null
to return the entities as instances of the class type T
.
* @param opContext
* An {@link OperationContext} object used to track the execution of the operation.
* @return
* An {@link ODataPayload} containing a collection of {@link TableResult} objects with the parsed operation
* response.
*
* @throws XMLStreamException
* if an error occurs while accessing the stream.
* @throws ParseException
* if an error occurs while parsing the stream.
* @throws InstantiationException
* if an error occurs while constructing the result.
* @throws IllegalAccessException
* if an error occurs in reflection while parsing the result.
* @throws StorageException
* if a storage service error occurs.
*/
@SuppressWarnings("unchecked")
protected static ODataPayload> parseResponse(final InputStream inStream,
final Class clazzType, final EntityResolver resolver, final OperationContext opContext)
throws XMLStreamException, ParseException, InstantiationException, IllegalAccessException, StorageException {
ODataPayload corePayload = null;
ODataPayload resolvedPayload = null;
ODataPayload> commonPayload = null;
if (resolver != null) {
resolvedPayload = new ODataPayload();
commonPayload = resolvedPayload;
}
else {
corePayload = new ODataPayload();
commonPayload = corePayload;
}
final XMLStreamReader xmlr = Utility.createXMLStreamReaderFromStream(inStream);
int eventType = xmlr.getEventType();
xmlr.require(XMLStreamConstants.START_DOCUMENT, null, null);
eventType = xmlr.next();
xmlr.require(XMLStreamConstants.START_ELEMENT, null, ODataConstants.FEED);
// skip feed chars
eventType = xmlr.next();
while (xmlr.hasNext()) {
eventType = xmlr.next();
if (eventType == XMLStreamConstants.CHARACTERS) {
xmlr.getText();
continue;
}
final String name = xmlr.getName().toString();
if (eventType == XMLStreamConstants.START_ELEMENT) {
if (name.equals(ODataConstants.BRACKETED_ATOM_NS + ODataConstants.ENTRY)) {
final TableResult res = parseEntity(xmlr, clazzType, resolver, opContext);
if (corePayload != null) {
corePayload.tableResults.add(res);
}
if (resolver != null) {
resolvedPayload.results.add((R) res.getResult());
}
else {
corePayload.results.add((T) res.getResult());
}
}
}
else if (eventType == XMLStreamConstants.END_ELEMENT
&& name.equals(ODataConstants.BRACKETED_ATOM_NS + ODataConstants.FEED)) {
break;
}
}
xmlr.require(XMLStreamConstants.END_ELEMENT, null, ODataConstants.FEED);
return commonPayload;
}
/**
* Reserved for internal use. Parses the operation response as an entity. Reads entity data from the specified
* XMLStreamReader
using the specified class type and optionally projects the entity result with the
* specified resolver into a {@link TableResult} object.
*
* @param xmlr
* The XMLStreamReader
to read the data to parse from.
* @param httpStatusCode
* The HTTP status code returned with the operation response.
* @param clazzType
* The class type T
implementing {@link TableEntity} for the entity returned. Set to
* null
to ignore the returned entity and copy only response properties into the
* {@link TableResult} object.
* @param resolver
* An {@link EntityResolver} instance to project the entity into an instance of type R
. Set
* to null
to return the entitys as instance of the class type T
.
* @param opContext
* An {@link OperationContext} object used to track the execution of the operation.
* @return
* A {@link TableResult} object with the parsed operation response.
*
* @throws XMLStreamException
* if an error occurs while accessing the stream.
* @throws ParseException
* if an error occurs while parsing the stream.
* @throws InstantiationException
* if an error occurs while constructing the result.
* @throws IllegalAccessException
* if an error occurs in reflection while parsing the result.
* @throws StorageException
* if a storage service error occurs.
*/
protected static TableResult parseSingleOpResponse(final XMLStreamReader xmlr,
final int httpStatusCode, final Class clazzType, final EntityResolver resolver,
final OperationContext opContext) throws XMLStreamException, ParseException, InstantiationException,
IllegalAccessException, StorageException {
xmlr.require(XMLStreamConstants.START_DOCUMENT, null, null);
xmlr.next();
final TableResult res = parseEntity(xmlr, clazzType, resolver, opContext);
res.setHttpStatusCode(httpStatusCode);
return res;
}
/**
* Reserved for internal use. Reads the properties of an entity from the stream into a map of property names to
* typed values. Reads the entity data as an AtomPub Entry Resource from the specified {@link XMLStreamReader} into
* a map of String
property names to {@link EntityProperty} data typed values.
*
* @param xmlr
* The XMLStreamReader
to read the data from.
* @param opContext
* An {@link OperationContext} object used to track the execution of the operation.
*
* @return
* A java.util.HashMap
containing a map of String
property names to
* {@link EntityProperty} data typed values found in the entity data.
* @throws XMLStreamException
* if an error occurs accessing the stream.
* @throws ParseException
* if an error occurs converting the input to a particular data type.
*/
protected static HashMap readProperties(final XMLStreamReader xmlr,
final OperationContext opContext) throws XMLStreamException, ParseException {
int eventType = xmlr.getEventType();
xmlr.require(XMLStreamConstants.START_ELEMENT, null, ODataConstants.PROPERTIES);
final HashMap properties = new HashMap();
while (xmlr.hasNext()) {
eventType = xmlr.next();
if (eventType == XMLStreamConstants.CHARACTERS) {
xmlr.getText();
continue;
}
if (eventType == XMLStreamConstants.START_ELEMENT
&& xmlr.getNamespaceURI().equals(ODataConstants.DATA_SERVICES_NS)) {
final String key = xmlr.getLocalName();
String val = Constants.EMPTY_STRING;
String edmType = null;
if (xmlr.getAttributeCount() > 0) {
edmType = xmlr.getAttributeValue(ODataConstants.DATA_SERVICES_METADATA_NS, ODataConstants.TYPE);
}
// move to chars
eventType = xmlr.next();
if (eventType == XMLStreamConstants.CHARACTERS) {
val = xmlr.getText();
// end element
eventType = xmlr.next();
}
xmlr.require(XMLStreamConstants.END_ELEMENT, null, key);
final EntityProperty newProp = new EntityProperty(val, EdmType.parse(edmType));
properties.put(key, newProp);
}
else if (eventType == XMLStreamConstants.END_ELEMENT
&& xmlr.getName().toString()
.equals(ODataConstants.BRACKETED_DATA_SERVICES_METADATA_NS + ODataConstants.PROPERTIES)) {
// End read properties
break;
}
}
xmlr.require(XMLStreamConstants.END_ELEMENT, null, ODataConstants.PROPERTIES);
return properties;
}
/**
* Reserved for internal use. Writes an entity to the stream as an AtomPub Entry Resource, leaving the stream open
* for additional writing.
*
* @param entity
* The instance implementing {@link TableEntity} to write to the output stream.
* @param isTableEntry
* A flag indicating the entity is a reference to a table at the top level of the storage service when
* true and a reference to an entity within a table when false
.
* @param xmlw
* The XMLStreamWriter
to write the entity to.
* @param opContext
* An {@link OperationContext} object used to track the execution of the operation.
*
* @throws XMLStreamException
* if an error occurs accessing the stream.
* @throws StorageException
* if a Storage service error occurs.
*/
protected static void writeEntityToStream(final TableEntity entity, final boolean isTableEntry,
final XMLStreamWriter xmlw, final OperationContext opContext) throws XMLStreamException, StorageException {
final HashMap properties = entity.writeEntity(opContext);
if (properties == null) {
throw new IllegalArgumentException("Entity did not produce properties to serialize");
}
if (!isTableEntry) {
Utility.assertNotNull(TableConstants.PARTITION_KEY, entity.getPartitionKey());
Utility.assertNotNull(TableConstants.ROW_KEY, entity.getRowKey());
Utility.assertNotNull(TableConstants.TIMESTAMP, entity.getTimestamp());
}
// Begin entry
xmlw.writeStartElement("entry");
xmlw.writeNamespace("d", ODataConstants.DATA_SERVICES_NS);
xmlw.writeNamespace("m", ODataConstants.DATA_SERVICES_METADATA_NS);
// default namespace
xmlw.writeNamespace(null, ODataConstants.ATOM_NS);
// Content
xmlw.writeStartElement(ODataConstants.CONTENT);
xmlw.writeAttribute(ODataConstants.TYPE, ODataConstants.ODATA_CONTENT_TYPE);
// m:properties
xmlw.writeStartElement("m", ODataConstants.PROPERTIES, ODataConstants.DATA_SERVICES_METADATA_NS);
if (!isTableEntry) {
// d:PartitionKey
xmlw.writeStartElement("d", TableConstants.PARTITION_KEY, ODataConstants.DATA_SERVICES_NS);
xmlw.writeAttribute("xml", "xml", "space", "preserve");
xmlw.writeCharacters(entity.getPartitionKey());
xmlw.writeEndElement();
// d:RowKey
xmlw.writeStartElement("d", TableConstants.ROW_KEY, ODataConstants.DATA_SERVICES_NS);
xmlw.writeAttribute("xml", "xml", "space", "preserve");
xmlw.writeCharacters(entity.getRowKey());
xmlw.writeEndElement();
// d:Timestamp
if (entity.getTimestamp() == null) {
entity.setTimestamp(new Date());
}
xmlw.writeStartElement("d", TableConstants.TIMESTAMP, ODataConstants.DATA_SERVICES_NS);
xmlw.writeAttribute("m", ODataConstants.DATA_SERVICES_METADATA_NS, ODataConstants.TYPE,
EdmType.DATE_TIME.toString());
xmlw.writeCharacters(Utility.getTimeByZoneAndFormat(entity.getTimestamp(), Utility.UTC_ZONE,
Utility.ISO8061_LONG_PATTERN));
xmlw.writeEndElement();
}
for (final Entry ent : properties.entrySet()) {
if (ent.getKey().equals(TableConstants.PARTITION_KEY) || ent.getKey().equals(TableConstants.ROW_KEY)
|| ent.getKey().equals(TableConstants.TIMESTAMP) || ent.getKey().equals("Etag")) {
continue;
}
EntityProperty currProp = ent.getValue();
// d:PropName
xmlw.writeStartElement("d", ent.getKey(), ODataConstants.DATA_SERVICES_NS);
if (currProp.getEdmType() == EdmType.STRING) {
xmlw.writeAttribute("xml", "xml", "space", "preserve");
}
else if (currProp.getEdmType().toString().length() != 0) {
String edmTypeString = currProp.getEdmType().toString();
if (edmTypeString.length() != 0) {
xmlw.writeAttribute("m", ODataConstants.DATA_SERVICES_METADATA_NS, ODataConstants.TYPE,
edmTypeString);
}
}
if (currProp.getIsNull()) {
xmlw.writeAttribute("m", ODataConstants.DATA_SERVICES_METADATA_NS, ODataConstants.NULL, Constants.TRUE);
}
// Write Value
xmlw.writeCharacters(currProp.getValueAsString());
// End d:PropName
xmlw.writeEndElement();
}
// End m:properties
xmlw.writeEndElement();
// End content
xmlw.writeEndElement();
// End entry
xmlw.writeEndElement();
}
/**
* Reserved for internal use. Writes a single entity to the specified OutputStream
as a complete XML
* document.
*
* @param entity
* The instance implementing {@link TableEntity} to write to the output stream.
* @param isTableEntry
* A flag indicating the entity is a reference to a table at the top level of the storage service when
* true and a reference to an entity within a table when false
.
* @param outStream
* The OutputStream
to write the entity to.
* @param opContext
* An {@link OperationContext} object used to track the execution of the operation.
*
* @throws XMLStreamException
* if an error occurs creating or accessing the stream.
* @throws StorageException
* if a Storage service error occurs.
*/
protected static void writeSingleEntityToStream(final TableEntity entity, final boolean isTableEntry,
final OutputStream outStream, final OperationContext opContext) throws XMLStreamException, StorageException {
final XMLStreamWriter xmlw = AtomPubParser.generateTableWriter(outStream);
writeSingleEntityToStream(entity, isTableEntry, xmlw, opContext);
}
/**
* Reserved for internal use. Writes a single entity to the specified XMLStreamWriter
as a complete XML
* document.
*
* @param entity
* The instance implementing {@link TableEntity} to write to the output stream.
* @param isTableEntry
* A flag indicating the entity is a reference to a table at the top level of the storage service when
* true and a reference to an entity within a table when false
.
* @param xmlw
* The XMLStreamWriter
to write the entity to.
* @param opContext
* An {@link OperationContext} object used to track the execution of the operation.
*
* @throws XMLStreamException
* if an error occurs creating or accessing the stream.
* @throws StorageException
* if a Storage service error occurs.
*/
protected static void writeSingleEntityToStream(final TableEntity entity, final boolean isTableEntry,
final XMLStreamWriter xmlw, final OperationContext opContext) throws XMLStreamException, StorageException {
// default is UTF8
xmlw.writeStartDocument("UTF-8", "1.0");
writeEntityToStream(entity, isTableEntry, xmlw, opContext);
// end doc
xmlw.writeEndDocument();
xmlw.flush();
}
}