/******************************************************************************* * Copyright 2013 SAP AG * * 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.sap.core.odata.core.ep.consumer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import com.sap.core.odata.api.edm.Edm; import com.sap.core.odata.api.ep.EntityProviderException; import com.sap.core.odata.api.ep.EntityProviderReadProperties; import com.sap.core.odata.api.ep.entry.ODataEntry; import com.sap.core.odata.api.ep.feed.ODataFeed; import com.sap.core.odata.core.ep.aggregator.EntityInfoAggregator; import com.sap.core.odata.core.ep.feed.FeedMetadataImpl; import com.sap.core.odata.core.ep.feed.ODataFeedImpl; import com.sap.core.odata.core.ep.util.FormatXml; /** * Atom/XML format reader/consumer for feeds. * * {@link XmlFeedConsumer} instance use {@link XmlEntryConsumer#readEntry(XMLStreamReader, EntityInfoAggregator, EntityProviderReadProperties)} * for read/consume of several entries. * * @author SAP AG */ public class XmlFeedConsumer { /** * * @param reader * @param eia * @param readProperties * @return {@link ODataFeed} object * @throws EntityProviderException */ public ODataFeed readFeed(final XMLStreamReader reader, final EntityInfoAggregator eia, final EntityProviderReadProperties readProperties) throws EntityProviderException { try { // read xml tag reader.require(XMLStreamConstants.START_DOCUMENT, null, null); reader.nextTag(); // read feed tag reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_FEED); Map<String, String> foundPrefix2NamespaceUri = extractNamespacesFromTag(reader); foundPrefix2NamespaceUri.putAll(readProperties.getValidatedPrefixNamespaceUris()); checkAllMandatoryNamespacesAvailable(foundPrefix2NamespaceUri); EntityProviderReadProperties entryReadProperties = EntityProviderReadProperties.initFrom(readProperties).addValidatedPrefixes(foundPrefix2NamespaceUri).build(); // read feed data (metadata and entries) return readFeedData(reader, eia, entryReadProperties); } catch (XMLStreamException e) { throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e); } } /** * Read all feed specific data (like <code>inline count</code> and <code>next link</code>) as well as all feed entries (<code>entry</code>). * * @param reader * @param eia * @param entryReadProperties * @return * @throws XMLStreamException * @throws EntityProviderException */ private ODataFeed readFeedData(final XMLStreamReader reader, final EntityInfoAggregator eia, final EntityProviderReadProperties entryReadProperties) throws XMLStreamException, EntityProviderException { FeedMetadataImpl metadata = new FeedMetadataImpl(); XmlEntryConsumer xec = new XmlEntryConsumer(); List<ODataEntry> results = new ArrayList<ODataEntry>(); while (reader.hasNext() && !isFeedEndTag(reader)) { if (FormatXml.ATOM_ENTRY.equals(reader.getLocalName())) { ODataEntry entry = xec.readEntry(reader, eia, entryReadProperties); results.add(entry); } else if (FormatXml.M_COUNT.equals(reader.getLocalName())) { reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_M_2007_08, FormatXml.M_COUNT); reader.next(); if (reader.hasText()) { String inlineCountString = reader.getText(); try { int inlineCountNumber = Integer.valueOf(inlineCountString); if (inlineCountNumber >= 0) { metadata.setInlineCount(inlineCountNumber); } else { throw new EntityProviderException(EntityProviderException.INLINECOUNT_INVALID.addContent(inlineCountNumber)); } } catch (NumberFormatException e) { throw new EntityProviderException(EntityProviderException.INLINECOUNT_INVALID.addContent(""), e); } } } else if (FormatXml.ATOM_LINK.equals(reader.getLocalName())) { reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_LINK); final String rel = reader.getAttributeValue(null, FormatXml.ATOM_REL); if (FormatXml.ATOM_NEXT_LINK.equals(rel)) { final String uri = reader.getAttributeValue(null, FormatXml.ATOM_HREF); metadata.setNextLink(uri); } else if (FormatXml.ATOM_DELTA_LINK.equals(rel)) { final String uri = reader.getAttributeValue(null, FormatXml.ATOM_HREF); metadata.setDeltaLink(uri); } reader.next(); } else { reader.next(); } readTillNextStartTag(reader); } return new ODataFeedImpl(results, metadata); } private void readTillNextStartTag(final XMLStreamReader reader) throws XMLStreamException { while (reader.hasNext() && !reader.isStartElement()) { reader.next(); } } private boolean isFeedEndTag(final XMLStreamReader reader) { return reader.isEndElement() && Edm.NAMESPACE_ATOM_2005.equals(reader.getNamespaceURI()) && FormatXml.ATOM_FEED.equals(reader.getLocalName()); } /** * * @param reader * @return * @throws EntityProviderException */ private Map<String, String> extractNamespacesFromTag(final XMLStreamReader reader) throws EntityProviderException { // collect namespaces Map<String, String> foundPrefix2NamespaceUri = new HashMap<String, String>(); int namespaceCount = reader.getNamespaceCount(); for (int i = 0; i < namespaceCount; i++) { String namespacePrefix = reader.getNamespacePrefix(i); String namespaceUri = reader.getNamespaceURI(i); foundPrefix2NamespaceUri.put(namespacePrefix, namespaceUri); } return foundPrefix2NamespaceUri; } /** * * @param foundPrefix2NamespaceUri * @throws EntityProviderException */ private void checkAllMandatoryNamespacesAvailable(final Map<String, String> foundPrefix2NamespaceUri) throws EntityProviderException { if (!foundPrefix2NamespaceUri.containsValue(Edm.NAMESPACE_D_2007_08)) { throw new EntityProviderException(EntityProviderException.INVALID_NAMESPACE.addContent(Edm.NAMESPACE_D_2007_08)); } else if (!foundPrefix2NamespaceUri.containsValue(Edm.NAMESPACE_M_2007_08)) { throw new EntityProviderException(EntityProviderException.INVALID_NAMESPACE.addContent(Edm.NAMESPACE_M_2007_08)); } else if (!foundPrefix2NamespaceUri.containsValue(Edm.NAMESPACE_ATOM_2005)) { throw new EntityProviderException(EntityProviderException.INVALID_NAMESPACE.addContent(Edm.NAMESPACE_ATOM_2005)); } } }