org.apache.olingo.fit.utils.AbstractXMLUtilities.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.olingo.fit.utils.AbstractXMLUtilities.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.olingo.fit.utils;

import static org.apache.olingo.fit.utils.Constants.*;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.util.AbstractMap;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.NotFoundException;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;

public abstract class AbstractXMLUtilities extends AbstractUtilities {

    protected static XMLInputFactory ifactory = null;

    protected static XMLOutputFactory ofactory = null;

    public AbstractXMLUtilities(final ODataVersion version) throws Exception {
        super(version);
    }

    public abstract void retrieveLinkInfoFromMetadata() throws Exception;

    @Override
    protected Accept getDefaultFormat() {
        return Accept.ATOM;
    }

    protected XMLEventReader getEventReader(final InputStream is) throws XMLStreamException {
        if (ifactory == null) {
            ifactory = XMLInputFactory.newInstance();
        }
        ifactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, false);
        return ifactory.createXMLEventReader(is);
    }

    protected static XMLEventWriter getEventWriter(final OutputStream os) throws XMLStreamException {
        if (ofactory == null) {
            ofactory = XMLOutputFactory.newInstance();
        }

        return ofactory.createXMLEventWriter(os);
    }

    private void writeEvent(final XMLEvent event, final XMLEventWriter writer) {
        if (writer != null) {
            try {
                writer.add(event);
            } catch (XMLStreamException e) {
                LOG.error("Error writing event {}", event, e);
            }
        }
    }

    private void skipElement(final StartElement start, final XMLEventReader reader, final XMLEventWriter writer,
            final boolean excludeStart) throws Exception {

        if (!excludeStart) {
            writeEvent(start, writer);
        }

        int depth = 1;
        boolean found = false;

        while (reader.hasNext() && !found) {
            final XMLEvent event = reader.nextEvent();

            writeEvent(event, writer);

            if (event.getEventType() == XMLStreamConstants.START_ELEMENT) {
                depth++;
            } else if (event.getEventType() == XMLStreamConstants.END_ELEMENT) {
                depth--;
                found = depth == 0 && start.getName().equals(event.asEndElement().getName());
            }
        }
    }

    /**
     * {@inheritDoc }
     */
    @Override
    protected InputStream addLinks(final String entitySetName, final String entitykey, final InputStream is,
            final Set<String> links) throws Exception {

        // -----------------------------------------
        // 0. Build reader and writer
        // -----------------------------------------
        final XMLEventReader reader = getEventReader(is);
        final XMLEventFactory eventFactory = XMLEventFactory.newInstance();

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final XMLEventWriter writer = getEventWriter(bos);
        // -----------------------------------------
        final Map.Entry<Integer, XmlElement> entry = extractElement(reader, writer,
                Collections.singletonList("entry"), 0, 1, 1);

        writer.add(entry.getValue().getStart());

        // add for links
        for (String link : links) {
            final Set<Attribute> attributes = new HashSet<Attribute>();
            attributes.add(eventFactory.createAttribute(new QName("title"), link));
            attributes.add(eventFactory.createAttribute(new QName("href"),
                    Commons.getLinksURI(version, entitySetName, entitykey, link)));
            attributes.add(eventFactory.createAttribute(new QName("rel"), Constants.ATOM_LINK_REL + link));
            attributes.add(eventFactory.createAttribute(new QName("type"),
                    Commons.linkInfo.get(version).isFeed(entitySetName, link) ? Constants.ATOM_LINK_FEED
                            : Constants.ATOM_LINK_ENTRY));

            writer.add(eventFactory.createStartElement(new QName(LINK), attributes.iterator(), null));
            writer.add(eventFactory.createEndElement(new QName(LINK), null));
        }

        writer.add(entry.getValue().getContentReader());
        writer.add(entry.getValue().getEnd());
        writer.add(reader);
        IOUtils.closeQuietly(is);

        writer.flush();
        writer.close();
        reader.close();

        return new ByteArrayInputStream(bos.toByteArray());
    }

    /**
     * {@inheritDoc }
     */
    @Override
    protected Set<String> retrieveAllLinkNames(final InputStream is) throws Exception {
        final Set<String> links = new HashSet<String>();

        final XMLEventReader reader = getEventReader(is);

        try {

            int startDepth = 0;

            while (true) {
                final Map.Entry<Integer, XmlElement> linkInfo = extractElement(reader, null,
                        Collections.<String>singletonList(LINK), startDepth, 2, 2);

                startDepth = linkInfo.getKey();

                links.add(linkInfo.getValue().getStart().getAttributeByName(new QName("title")).getValue());
            }
        } catch (Exception ignore) {
            // ignore
        } finally {
            reader.close();
            IOUtils.closeQuietly(is);
        }

        return links;
    }

    /**
     * {@inheritDoc }
     */
    @Override
    protected NavigationLinks retrieveNavigationInfo(final String entitySetName, final InputStream is)
            throws Exception {

        final NavigationLinks links = new NavigationLinks();

        final XMLEventReader reader = getEventReader(is);

        try {
            final List<Map.Entry<String, String>> filter = new ArrayList<Map.Entry<String, String>>();
            filter.add(new AbstractMap.SimpleEntry<String, String>("type", "application/atom+xml;type=entry"));
            filter.add(new AbstractMap.SimpleEntry<String, String>("type", "application/atom+xml;type=feed"));

            int startDepth = 0;

            while (true) {
                // a. search for link with type attribute equals to "application/atom+xml;type=entry/feed"
                final Map.Entry<Integer, XmlElement> linkInfo = extractElement(reader, null,
                        Collections.<String>singletonList(LINK), filter, true, startDepth, 2, 2);
                final XmlElement link = linkInfo.getValue();
                startDepth = linkInfo.getKey();

                final String title = link.getStart().getAttributeByName(new QName("title")).getValue();

                final Attribute hrefAttr = link.getStart().getAttributeByName(new QName("href"));
                final String href = hrefAttr == null ? null : hrefAttr.getValue();

                try {
                    final XmlElement inlineElement = extractElement(link.getContentReader(), null,
                            Collections.<String>singletonList(INLINE), 0, -1, -1).getValue();
                    final XMLEventReader inlineReader = inlineElement.getContentReader();

                    try {
                        while (true) {
                            final XmlElement entry = extractElement(inlineReader, null,
                                    Collections.<String>singletonList("entry"), 0, -1, -1).getValue();
                            links.addInlines(title, entry.toStream());
                        }
                    } catch (Exception e) {
                        // Reached the end of document
                    }

                    inlineReader.close();
                } catch (Exception ignore) {
                    // inline element not found (inlines are not mondatory).
                    if (StringUtils.isNotBlank(href) && entityUriPattern.matcher(href).matches()) {
                        links.addLinks(title, href.substring(href.lastIndexOf('/') + 1));
                    }
                }
            }
        } catch (Exception ignore) {
            // ignore
        } finally {
            reader.close();
        }

        return links;
    }

    /**
     * {@inheritDoc }
     */
    @Override
    protected InputStream normalizeLinks(final String entitySetName, final String entityKey, final InputStream is,
            final NavigationLinks links) throws Exception {

        // -----------------------------------------
        // 0. Build reader and writer
        // -----------------------------------------
        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        IOUtils.copy(is, bos);
        is.close();

        final ByteArrayOutputStream tmpBos = new ByteArrayOutputStream();
        final XMLEventWriter writer = getEventWriter(tmpBos);

        final XMLEventReader reader = getEventReader(new ByteArrayInputStream(bos.toByteArray()));
        // -----------------------------------------

        // -----------------------------------------
        // 1. Normalize links
        // -----------------------------------------
        final Set<String> added = new HashSet<String>();

        try {
            final List<Map.Entry<String, String>> filter = new ArrayList<Map.Entry<String, String>>();
            filter.add(new AbstractMap.SimpleEntry<String, String>("type", "application/atom+xml;type=entry"));
            filter.add(new AbstractMap.SimpleEntry<String, String>("type", "application/atom+xml;type=feed"));

            Map.Entry<Integer, XmlElement> linkInfo = null;

            while (true) {
                // a. search for link with type attribute equals to "application/atom+xml;type=entry/feed"
                linkInfo = extractElement(reader, writer, Collections.<String>singletonList(LINK), filter, true,
                        linkInfo == null ? 0 : linkInfo.getKey(), 2, 2);
                final XmlElement link = linkInfo.getValue();

                final String title = link.getStart().getAttributeByName(new QName("title")).getValue();

                if (!added.contains(title)) {
                    added.add(title);

                    final String normalizedLink = String.format(
                            "<link href=\"%s(%s)/%s\" rel=\"%s\" title=\"%s\" type=\"%s\"/>", entitySetName,
                            entityKey, title, link.getStart().getAttributeByName(new QName("rel")).getValue(),
                            title, link.getStart().getAttributeByName(new QName("type")).getValue());

                    addAtomElement(IOUtils.toInputStream(normalizedLink), writer);
                }
            }
        } catch (Exception ignore) {
            // ignore
        } finally {
            writer.close();
            reader.close();
        }
        // -----------------------------------------

        // -----------------------------------------
        // 2. Add edit link if missing
        // -----------------------------------------
        final InputStream content = addEditLink(new ByteArrayInputStream(tmpBos.toByteArray()), entitySetName,
                Constants.DEFAULT_SERVICE_URL + entitySetName + "(" + entityKey + ")");
        // -----------------------------------------

        // -----------------------------------------
        // 3. Add content element if missing
        // -----------------------------------------
        return addAtomContent(content, entitySetName,
                Constants.DEFAULT_SERVICE_URL + entitySetName + "(" + entityKey + ")");
        // -----------------------------------------

    }

    public XmlElement getXmlElement(final StartElement start, final XMLEventReader reader) throws Exception {

        final XmlElement res = new XmlElement();
        res.setStart(start);

        StringWriter content = new StringWriter();

        int depth = 1;

        while (reader.hasNext() && depth > 0) {
            final XMLEvent event = reader.nextEvent();

            if (event.getEventType() == XMLStreamConstants.START_ELEMENT) {
                depth++;
            } else if (event.getEventType() == XMLStreamConstants.END_ELEMENT) {
                depth--;
            }

            if (depth == 0) {
                res.setEnd(event.asEndElement());
            } else {
                event.writeAsEncodedUnicode(content);
            }
        }

        content.flush();
        content.close();

        res.setContent(new ByteArrayInputStream(content.toString().getBytes()));

        return res;
    }

    private void addAtomElement(final InputStream content, final XMLEventWriter writer) throws Exception {
        final XMLEventReader reader = getEventReader(content);

        final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
        XMLEvent newLine = eventFactory.createSpace("\n");

        try {
            writer.add(newLine);

            while (reader.hasNext()) {
                final XMLEvent event = reader.nextEvent();

                if (event.getEventType() != XMLStreamConstants.START_DOCUMENT
                        && event.getEventType() != XMLStreamConstants.END_DOCUMENT
                        && event.getEventType() != XMLStreamConstants.COMMENT) {
                    writer.add(event);
                }
            }
            writer.add(newLine);
        } finally {
            reader.close();
            IOUtils.closeQuietly(content);
        }
    }

    @Override
    public InputStream addEditLink(final InputStream content, final String title, final String href)
            throws Exception {

        final ByteArrayOutputStream copy = new ByteArrayOutputStream();
        IOUtils.copy(content, copy);

        IOUtils.closeQuietly(content);

        XMLEventReader reader = getEventReader(new ByteArrayInputStream(copy.toByteArray()));

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        XMLEventWriter writer = getEventWriter(bos);

        final String editLinkElement = String.format("<link rel=\"edit\" title=\"%s\" href=\"%s\" />", title, href);

        try {
            // check edit link existence
            extractElement(reader, writer, Collections.<String>singletonList(LINK),
                    Collections.<Map.Entry<String, String>>singletonList(
                            new AbstractMap.SimpleEntry<String, String>("rel", "edit")),
                    false, 0, -1, -1);

            addAtomElement(IOUtils.toInputStream(editLinkElement), writer);
            writer.add(reader);

        } catch (Exception e) {
            reader.close();
            reader = getEventReader(new ByteArrayInputStream(copy.toByteArray()));

            bos = new ByteArrayOutputStream();
            writer = getEventWriter(bos);

            final XmlElement entryElement = extractElement(reader, writer,
                    Collections.<String>singletonList("entry"), 0, 1, 1).getValue();

            writer.add(entryElement.getStart());

            addAtomElement(IOUtils.toInputStream(editLinkElement), writer);

            writer.add(entryElement.getContentReader());
            writer.add(entryElement.getEnd());

            writer.add(reader);

            writer.flush();
            writer.close();
        } finally {
            reader.close();
        }

        return new ByteArrayInputStream(bos.toByteArray());
    }

    public InputStream addAtomContent(final InputStream content, final String title, final String href)
            throws Exception {

        final ByteArrayOutputStream copy = new ByteArrayOutputStream();
        IOUtils.copy(content, copy);

        IOUtils.closeQuietly(content);

        XMLEventReader reader = getEventReader(new ByteArrayInputStream(copy.toByteArray()));

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        XMLEventWriter writer = getEventWriter(bos);

        try {
            // check edit link existence
            XmlElement contentElement = extractElement(reader, writer, Collections.<String>singletonList("content"),
                    0, 2, 2).getValue();
            writer.add(contentElement.getStart());
            writer.add(contentElement.getContentReader());
            writer.add(contentElement.getEnd());
            writer.add(reader);
        } catch (Exception e) {
            reader.close();
            reader = getEventReader(new ByteArrayInputStream(copy.toByteArray()));

            bos = new ByteArrayOutputStream();
            writer = getEventWriter(bos);

            if (isMediaContent(title)) {
                final XmlElement entryElement = extractElement(reader, writer,
                        Collections.<String>singletonList("entry"), 0, 1, 1).getValue();

                writer.add(entryElement.getStart());
                writer.add(entryElement.getContentReader());

                addAtomElement(
                        IOUtils.toInputStream(String.format("<content type=\"*/*\" src=\"%s/$value\" />", href)),
                        writer);

                writer.add(entryElement.getEnd());
            } else {
                try {
                    final XmlElement entryElement = extractElement(reader, writer,
                            Collections.<String>singletonList(PROPERTIES), 0, 2, 3).getValue();

                    addAtomElement(IOUtils.toInputStream("<content type=\"application/xml\">"), writer);

                    writer.add(entryElement.getStart());
                    writer.add(entryElement.getContentReader());
                    writer.add(entryElement.getEnd());

                    addAtomElement(IOUtils.toInputStream("</content>"), writer);
                } catch (Exception nf) {
                    reader.close();
                    reader = getEventReader(new ByteArrayInputStream(copy.toByteArray()));

                    bos = new ByteArrayOutputStream();
                    writer = getEventWriter(bos);

                    final XmlElement entryElement = extractElement(reader, writer,
                            Collections.<String>singletonList("entry"), 0, 1, 1).getValue();
                    writer.add(entryElement.getStart());
                    writer.add(entryElement.getContentReader());

                    addAtomElement(IOUtils.toInputStream("<content type=\"application/xml\"/>"), writer);

                    writer.add(entryElement.getEnd());
                }
            }

            writer.add(reader);

            writer.flush();
            writer.close();
        } finally {
            reader.close();
        }

        return new ByteArrayInputStream(bos.toByteArray());
    }

    public int countAllElements(final String entitySetName) throws Exception {
        final String basePath = entitySetName + File.separatorChar;
        int count = countFeedElements(fsManager.readFile(basePath + FEED, Accept.XML), "entry");

        final String skipTokenDirPath = fsManager.getAbsolutePath(basePath + SKIP_TOKEN, null);

        try {
            final FileObject skipToken = fsManager.resolve(skipTokenDirPath);
            final FileObject[] files = fsManager.findByExtension(skipToken, Accept.XML.getExtension().substring(1));

            for (FileObject file : files) {
                count += countFeedElements(
                        fsManager.readFile(
                                basePath + SKIP_TOKEN + File.separatorChar + file.getName().getBaseName(), null),
                        "entry");
            }
        } catch (FileSystemException fse) {
            LOG.debug("Resource path '{}' not found", skipTokenDirPath);
        }

        return count;
    }

    private int countFeedElements(final InputStream is, final String elementName) throws XMLStreamException {
        final XMLEventReader reader = getEventReader(is);

        int count = 0;

        while (reader.hasNext()) {
            final XMLEvent event = reader.nextEvent();

            if (event.getEventType() == XMLStreamConstants.START_ELEMENT
                    && elementName.equals(event.asStartElement().getName().getLocalPart())) {
                count++;
            }
        }

        reader.close();
        return count;
    }

    public Map.Entry<Integer, XmlElement> extractElement(final XMLEventReader reader, final XMLEventWriter writer,
            final List<String> path, final int startPathPos, final int minPathPos, final int maxPathPos)
            throws Exception {
        return extractElement(reader, writer, path, null, false, startPathPos, minPathPos, maxPathPos);
    }

    public Map.Entry<Integer, XmlElement> extractElement(final XMLEventReader reader, final XMLEventWriter writer,
            final List<String> path, final Collection<Map.Entry<String, String>> filter, final boolean filterInOr,
            final int startPathPos, final int minPathPos, final int maxPathPos) throws Exception {

        StartElement start = null;
        int searchFor = 0;
        int depth = startPathPos;

        // Current inspected element
        String current = null;

        // set defaults
        final List<String> pathElementNames = path == null ? Collections.<String>emptyList() : path;
        final Collection<Map.Entry<String, String>> filterAttrs = filter == null
                ? Collections.<Map.Entry<String, String>>emptySet()
                : filter;

        while (reader.hasNext() && start == null) {
            final XMLEvent event = reader.nextEvent();

            if (event.getEventType() == XMLStreamConstants.START_ELEMENT) {
                depth++;

                if (current != null
                        || ((minPathPos < 0 || minPathPos <= depth) && (maxPathPos < 0 || depth <= maxPathPos))) {
                    if (pathElementNames.isEmpty() || pathElementNames.get(searchFor).trim()
                            .equals(event.asStartElement().getName().getLocalPart())) {

                        if (searchFor < pathElementNames.size() - 1) {
                            // path exploring not completed
                            writeEvent(event, writer);
                            current = pathElementNames.get(searchFor).trim();
                            searchFor++;
                        } else {

                            // path exploring completed ... evaluate filter about path element name attribute
                            boolean match = filterAttrs.isEmpty() || !filterInOr;

                            for (Map.Entry<String, String> filterAttr : filterAttrs) {
                                final Attribute attr = event.asStartElement()
                                        .getAttributeByName(new QName(filterAttr.getKey().trim()));

                                if (attr == null || !filterAttr.getValue().trim().equals(attr.getValue())) {
                                    match = filterInOr ? match : false;
                                } else {
                                    match = filterInOr ? true : match;
                                }
                            }

                            if (match) {
                                // found searched element
                                start = event.asStartElement();
                            } else {
                                skipElement(event.asStartElement(), reader, writer, false);
                                depth--;
                            }
                        }
                    } else if (current == null) {
                        writeEvent(event, writer);
                    } else {
                        // skip element
                        skipElement(event.asStartElement(), reader, writer, false);
                        depth--;
                    }
                } else {
                    writeEvent(event, writer);
                }

            } else if (event.getEventType() == XMLStreamConstants.END_ELEMENT) {
                depth--;

                writeEvent(event, writer);

                if (event.asEndElement().getName().getLocalPart().equals(current)) {
                    // back step ....
                    searchFor--;
                    current = searchFor > 0 ? pathElementNames.get(searchFor - 1).trim() : null;
                }
            } else {
                writeEvent(event, writer);
            }
        }

        if (start == null) {
            throw new NotFoundException();
        }

        return new SimpleEntry<Integer, XmlElement>(Integer.valueOf(depth - 1), getXmlElement(start, reader));
    }

    public InputStream addAtomInlinecount(final InputStream feed, final int count, final Accept accept)
            throws Exception {
        final XMLEventReader reader = getEventReader(feed);

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final XMLEventWriter writer = getEventWriter(bos);

        try {

            final XmlElement feedElement = extractElement(reader, writer, Collections.<String>singletonList("feed"),
                    0, 1, 1).getValue();

            writer.add(feedElement.getStart());
            addAtomElement(IOUtils.toInputStream(String.format("<m:count>%d</m:count>", count)), writer);
            writer.add(feedElement.getContentReader());
            writer.add(feedElement.getEnd());

            while (reader.hasNext()) {
                writer.add(reader.nextEvent());
            }

        } finally {
            writer.flush();
            writer.close();
            reader.close();
            IOUtils.closeQuietly(feed);
        }

        return new ByteArrayInputStream(bos.toByteArray());
    }

    @Override
    public InputStream getPropertyValue(final InputStream is, final List<String> path) throws Exception {

        final List<String> pathElements = new ArrayList<String>();

        for (String element : path) {
            pathElements.add(ATOM_PROPERTY_PREFIX + element);
        }

        final XMLEventReader reader = getEventReader(is);
        final Map.Entry<Integer, XmlElement> property = extractElement(reader, null, pathElements, 0, 3, 4);

        reader.close();
        IOUtils.closeQuietly(is);

        return property.getValue().getContent();
    }

    @Override
    public InputStream selectEntity(final InputStream entity, final String[] propertyNames) throws Exception {
        final XMLEventReader reader = getEventReader(entity);

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final XMLEventWriter writer = getEventWriter(bos);

        final List<String> found = new ArrayList<String>(Arrays.asList(propertyNames));

        boolean inProperties = false;
        boolean writeCurrent = true;
        Boolean writeNext = null;
        String currentName = null;

        final List<String> fieldToBeSaved = new ArrayList<String>(Arrays.asList(propertyNames));

        while (reader.hasNext()) {
            final XMLEvent event = reader.nextEvent();
            if (event.getEventType() == XMLStreamConstants.START_ELEMENT
                    && LINK.equals(event.asStartElement().getName().getLocalPart())
                    && !fieldToBeSaved
                            .contains(event.asStartElement().getAttributeByName(new QName("title")).getValue())
                    && !"edit".equals(event.asStartElement().getAttributeByName(new QName("rel")).getValue())) {
                writeCurrent = false;
            } else if (event.getEventType() == XMLStreamConstants.END_ELEMENT
                    && LINK.equals(event.asEndElement().getName().getLocalPart())) {
                writeNext = true;
            } else if (event.getEventType() == XMLStreamConstants.START_ELEMENT
                    && (PROPERTIES).equals(event.asStartElement().getName().getLocalPart())) {
                writeCurrent = true;
                writeNext = false;
                inProperties = true;
            } else if (event.getEventType() == XMLStreamConstants.END_ELEMENT
                    && (PROPERTIES).equals(event.asEndElement().getName().getLocalPart())) {
                writeCurrent = true;
            } else if (inProperties) {
                if (event.getEventType() == XMLStreamConstants.START_ELEMENT) {
                    final String elementName = event.asStartElement().getName().getLocalPart();

                    for (String propertyName : propertyNames) {
                        if ((ATOM_PROPERTY_PREFIX + propertyName.trim()).equals(elementName)) {
                            writeCurrent = true;
                            found.remove(propertyName);
                            currentName = propertyName;
                        }
                    }

                } else if (event.getEventType() == XMLStreamConstants.END_ELEMENT
                        && StringUtils.isNotBlank(currentName) && (ATOM_PROPERTY_PREFIX + currentName.trim())
                                .equals(event.asEndElement().getName().getLocalPart())) {
                    writeNext = false;
                    currentName = null;
                }

            }

            if (writeCurrent) {
                writer.add(event);
            }

            if (writeNext != null) {
                writeCurrent = writeNext;
                writeNext = null;
            }
        }

        writer.flush();
        writer.close();
        reader.close();
        IOUtils.closeQuietly(entity);

        // Do not raise any exception in order to support FC properties as well
        // if (!found.isEmpty()) {
        //     throw new Exception(String.format("Could not find a properties '%s'", found));
        // }

        return new ByteArrayInputStream(bos.toByteArray());
    }

    @Override
    public InputStream readEntities(final List<String> links, final String linkName, final String next,
            final boolean forceFeed) throws Exception {

        if (links.isEmpty()) {
            throw new NotFoundException();
        }

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();

        if (forceFeed || links.size() > 1) {
            // build a feed
            bos.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>".getBytes());

            bos.write(("<feed xml:base=\"" + DEFAULT_SERVICE_URL + "\" " + "xmlns=\"http://www.w3.org/2005/Atom\" "
                    + "xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" "
                    + "xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\">").getBytes());

            bos.write(("<id>" + DEFAULT_SERVICE_URL + "entityset(entityid)/" + linkName + "</id>").getBytes());

            bos.write(("<title type=\"text\">" + linkName + "</title>").getBytes());
            bos.write("<updated>2014-03-03T13:40:49Z</updated>".getBytes());
            bos.write(("<link rel=\"self\" title=\"" + linkName + "\" href=\"" + linkName + "\" />").getBytes());
        }

        for (String link : links) {
            try {
                final Map.Entry<String, String> uri = Commons.parseEntityURI(link);

                final XmlElement entry = extractElement(
                        getEventReader(readEntity(uri.getKey(), uri.getValue(), Accept.ATOM).getValue()), null,
                        Collections.<String>singletonList("entry"), 0, 1, 1).getValue();

                IOUtils.copy(entry.toStream(), bos);
            } catch (Exception e) {
                // log and ignore link
                LOG.warn("Error parsing uri {}", link, e);
            }
        }

        if (forceFeed || links.size() > 1) {

            if (StringUtils.isNotBlank(next)) {
                bos.write(String.format("<link rel=\"next\" href=\"%s\" />", next).getBytes());
            }

            bos.write("</feed>".getBytes());
        }

        return new ByteArrayInputStream(bos.toByteArray());
    }

    @Override
    public Map<String, InputStream> getChanges(final InputStream src) throws Exception {
        final Map<String, InputStream> res = new HashMap<String, InputStream>();

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        IOUtils.copy(src, bos);
        IOUtils.closeQuietly(src);

        // retrieve properties ...
        XMLEventReader reader = getEventReader(new ByteArrayInputStream(bos.toByteArray()));

        final Map.Entry<Integer, XmlElement> propertyElement = extractElement(reader, null,
                Collections.<String>singletonList(PROPERTIES), 0, 2, 3);
        reader.close();

        reader = propertyElement.getValue().getContentReader();

        try {
            while (true) {
                final XmlElement property = extractElement(reader, null, null, 0, -1, -1).getValue();
                res.put(property.getStart().getName().getLocalPart(), property.toStream());
            }
        } catch (Exception ignore) {
            // end
        }

        reader.close();

        // retrieve links ...
        reader = getEventReader(new ByteArrayInputStream(bos.toByteArray()));

        try {
            int pos = 0;
            while (true) {
                final Map.Entry<Integer, XmlElement> linkElement = extractElement(reader, null,
                        Collections.<String>singletonList(LINK), pos, 2, 2);

                res.put("[LINK]"
                        + linkElement.getValue().getStart().getAttributeByName(new QName("title")).getValue(),
                        linkElement.getValue().toStream());

                pos = linkElement.getKey();
            }
        } catch (Exception ignore) {
            // end
        }

        return res;
    }

    @Override
    public InputStream setChanges(final InputStream toBeChanged, final Map<String, InputStream> properties)
            throws Exception {
        XMLEventReader reader = getEventReader(toBeChanged);

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        XMLEventWriter writer = getEventWriter(bos);

        // ---------------------------------
        // add property changes
        // ---------------------------------
        Map.Entry<Integer, XmlElement> propertyElement = extractElement(reader, writer,
                Collections.<String>singletonList(PROPERTIES), 0, 2, 3);

        writer.flush();

        ByteArrayOutputStream pbos = new ByteArrayOutputStream();
        OutputStreamWriter pwriter = new OutputStreamWriter(pbos);

        final XMLEventReader propertyReader = propertyElement.getValue().getContentReader();

        try {
            while (true) {
                final XmlElement property = extractElement(propertyReader, null, null, 0, -1, -1).getValue();
                final String name = property.getStart().getName().getLocalPart();

                if (properties.containsKey(name)) {
                    // replace
                    final InputStream replacement = properties.get(name);
                    properties.remove(property.getStart().getName().getLocalPart());
                    pwriter.append(IOUtils.toString(replacement));
                    IOUtils.closeQuietly(replacement);
                } else {
                    pwriter.append(IOUtils.toString(property.toStream()));
                }
            }
        } catch (Exception ignore) {
            // end
        }

        for (Map.Entry<String, InputStream> remains : properties.entrySet()) {
            if (!remains.getKey().startsWith("[LINK]")) {
                pwriter.append(IOUtils.toString(remains.getValue()));
                IOUtils.closeQuietly(remains.getValue());
            }
        }

        pwriter.flush();
        pwriter.close();

        writer.add(propertyElement.getValue().getStart());
        writer.add(new XMLEventReaderWrapper(new ByteArrayInputStream(pbos.toByteArray())));
        writer.add(propertyElement.getValue().getEnd());

        IOUtils.closeQuietly(pbos);

        writer.add(reader);
        reader.close();
        writer.flush();
        writer.close();
        // ---------------------------------

        // ---------------------------------
        // add navigationm changes
        // ---------------------------------

        // remove existent links
        for (Map.Entry<String, InputStream> remains : properties.entrySet()) {

            if (remains.getKey().startsWith("[LINK]")) {
                reader = getEventReader(new ByteArrayInputStream(bos.toByteArray()));

                bos.reset();
                writer = getEventWriter(bos);

                try {
                    final String linkName = remains.getKey().substring(remains.getKey().indexOf("]") + 1);

                    extractElement(reader, writer, Collections.<String>singletonList(LINK),
                            Collections.<Map.Entry<String, String>>singleton(
                                    new SimpleEntry<String, String>("title", linkName)),
                            false, 0, 2, 2);

                    writer.add(reader);

                } catch (Exception ignore) {
                    // ignore
                }

                writer.flush();
                writer.close();
            }
        }

        reader = getEventReader(new ByteArrayInputStream(bos.toByteArray()));

        bos.reset();
        writer = getEventWriter(bos);

        propertyElement = extractElement(reader, writer, Collections.<String>singletonList(CONTENT), 0, 2, 2);
        writer.flush();

        pbos.reset();
        pwriter = new OutputStreamWriter(pbos);

        for (Map.Entry<String, InputStream> remains : properties.entrySet()) {
            if (remains.getKey().startsWith("[LINK]")) {
                pwriter.append(IOUtils.toString(remains.getValue()));
                IOUtils.closeQuietly(remains.getValue());
            }
        }

        pwriter.flush();
        pwriter.close();

        writer.add(new XMLEventReaderWrapper(new ByteArrayInputStream(pbos.toByteArray())));
        IOUtils.closeQuietly(pbos);

        writer.add(propertyElement.getValue().getStart());
        writer.add(propertyElement.getValue().getContentReader());
        writer.add(propertyElement.getValue().getEnd());

        writer.add(reader);
        reader.close();
        writer.flush();
        writer.close();
        // ---------------------------------

        return new ByteArrayInputStream(bos.toByteArray());
    }

    @Override
    protected InputStream replaceLink(final InputStream toBeChanged, final String linkName,
            final InputStream replacement) throws Exception {
        final XMLEventReader reader = getEventReader(toBeChanged);

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final XMLEventWriter writer = getEventWriter(bos);

        final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
        XMLEvent newLine = eventFactory.createSpace("\n");

        try {
            final XmlElement linkElement = extractElement(reader, writer, Collections.<String>singletonList(LINK),
                    Collections.<Map.Entry<String, String>>singletonList(
                            new SimpleEntry<String, String>("title", linkName)),
                    false, 0, -1, -1).getValue();
            writer.add(linkElement.getStart());

            // ------------------------------------------
            // write inline ...
            // ------------------------------------------
            writer.add(newLine);
            writer.add(eventFactory.createStartElement("m", null, "inline"));

            addAtomElement(replacement, writer);

            writer.add(eventFactory.createEndElement("m", null, "inline"));
            writer.add(newLine);
            // ------------------------------------------

            writer.add(linkElement.getEnd());

            writer.add(reader);
            writer.flush();
            writer.close();
        } finally {
            reader.close();
            IOUtils.closeQuietly(toBeChanged);
        }

        return new ByteArrayInputStream(bos.toByteArray());
    }

    public String getEdmTypeFromAtom(final String entitySetName, final String entityId, final List<String> path)
            throws Exception {
        InputStream src = fsManager.readFile(Commons.getEntityBasePath(entitySetName, entityId) + ENTITY,
                Accept.XML);

        final List<String> atomPathElements = new ArrayList<String>();

        for (String element : path) {
            atomPathElements.add(ATOM_PROPERTY_PREFIX + element);
        }

        final Map.Entry<Integer, XmlElement> prop = extractElement(getEventReader(src), null, atomPathElements, 0,
                3, 4);
        IOUtils.closeQuietly(src);

        final Attribute type = prop.getValue().getStart().getAttributeByName(new QName(TYPE));

        final String edmType;

        if (type == null) {
            edmType = Constants.ATOM_DEF_TYPE;
        } else {
            edmType = type.getValue();
        }

        return edmType;
    }

    @Override
    public Map.Entry<String, List<String>> extractLinkURIs(final String entitySetName, final String entityId,
            final String linkName) throws Exception {
        final LinkInfo links = readLinks(entitySetName, entityId, linkName, Accept.XML);
        return extractLinkURIs(links.getLinks());
    }

    @Override
    public Map.Entry<String, List<String>> extractLinkURIs(final InputStream is) throws Exception {
        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        IOUtils.copy(is, bos);
        IOUtils.closeQuietly(is);

        XMLEventReader reader = getEventReader(new ByteArrayInputStream(bos.toByteArray()));
        final List<String> links = new ArrayList<String>();
        try {
            while (true) {
                links.add(IOUtils
                        .toString(extractElement(reader, null, Collections.<String>singletonList("uri"), 0, -1, -1)
                                .getValue().getContent()));
            }
        } catch (Exception ignore) {
            // End document reached ...
        }
        reader.close();

        String next;

        reader = getEventReader(new ByteArrayInputStream(bos.toByteArray()));
        try {
            next = IOUtils
                    .toString(extractElement(reader, null, Collections.<String>singletonList("next"), 0, -1, -1)
                            .getValue().getContent());
        } catch (Exception ignore) {
            // next link is not mandatory
            next = null;
        }
        reader.close();

        return new AbstractMap.SimpleEntry<String, List<String>>(next, links);
    }

    @Override
    public InputStream getProperty(final String entitySetName, final String entityId, final List<String> path,
            final String edmType) throws Exception {
        final List<String> pathElements = new ArrayList<String>();

        for (String element : path) {
            pathElements.add(ATOM_PROPERTY_PREFIX + element);
        }

        final InputStream src = fsManager.readFile(Commons.getEntityBasePath(entitySetName, entityId) + ENTITY,
                Accept.XML);

        final XMLEventReader reader = getEventReader(src);
        final XmlElement property = extractElement(reader, null, pathElements, 0, 3, 4).getValue();

        reader.close();
        IOUtils.closeQuietly(src);

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final XMLEventWriter writer = getEventWriter(bos);

        final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
        writer.add(eventFactory.createStartDocument("UTF-8", "1.0"));
        writer.add(property.getStart());

        if (property.getStart().getAttributeByName(new QName(ATOM_DATASERVICE_NS)) == null) {
            writer.add(eventFactory.createNamespace(ATOM_PROPERTY_PREFIX.substring(0, 1), DATASERVICES_NS));
        }
        if (property.getStart().getAttributeByName(new QName(ATOM_METADATA_NS)) == null) {
            writer.add(eventFactory.createNamespace(ATOM_METADATA_PREFIX.substring(0, 1), METADATA_NS));
        }

        writer.add(property.getContentReader());
        writer.add(property.getEnd());

        writer.flush();
        writer.close();

        return new ByteArrayInputStream(bos.toByteArray());
    }

    @Override
    public InputStream replaceProperty(final InputStream src, final InputStream replacement,
            final List<String> path, final boolean justValue) throws Exception {

        final List<String> pathElements = new ArrayList<String>();

        for (String element : path) {
            pathElements.add(ATOM_PROPERTY_PREFIX + element);
        }

        final XMLEventReader reader = getEventReader(src);

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final XMLEventWriter writer = getEventWriter(bos);

        final Map.Entry<Integer, XmlElement> element = extractElement(reader, writer, pathElements, 0, 3, 4);

        if (justValue) {
            writer.add(element.getValue().getStart());
        }

        final XMLEventReader changesReader = new XMLEventReaderWrapper(replacement);

        writer.add(changesReader);
        changesReader.close();
        IOUtils.closeQuietly(replacement);

        if (justValue) {
            writer.add(element.getValue().getEnd());
        }

        writer.add(reader);

        reader.close();
        IOUtils.closeQuietly(src);

        writer.flush();
        writer.close();

        return new ByteArrayInputStream(bos.toByteArray());
    }

    @Override
    public InputStream deleteProperty(final InputStream src, final List<String> path) throws Exception {

        final List<String> pathElements = new ArrayList<String>();

        for (String element : path) {
            pathElements.add(ATOM_PROPERTY_PREFIX + element);
        }

        final XMLEventReader reader = getEventReader(src);

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final XMLEventWriter writer = getEventWriter(bos);

        final Map.Entry<Integer, XmlElement> element = extractElement(reader, writer, pathElements, 0, 3, 4);

        final XMLEventReader changesReader = new XMLEventReaderWrapper(
                IOUtils.toInputStream(String.format("<%s m:null=\"true\" />", path.get(path.size() - 1))));

        writer.add(changesReader);
        changesReader.close();

        writer.add(reader);

        reader.close();
        IOUtils.closeQuietly(src);

        writer.flush();
        writer.close();

        return new ByteArrayInputStream(bos.toByteArray());
    }
}