@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(Constants.get(ConstantKey.LINK)),
          Collections.<Map.Entry<String, String>>singletonList(
              new AbstractMap.SimpleEntry<String, String>("rel", "edit")),
          false,
          0,
          -1,
          -1);

      addAtomElement(IOUtils.toInputStream(editLinkElement, Constants.ENCODING), 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, Constants.ENCODING), 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 addAtomInlinecount(final InputStream feed, final int count) 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), Constants.ENCODING),
          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
  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(Constants.get(ConstantKey.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());
  }
  private 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(Constants.get(ConstantKey.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());
  }
  /** {@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(Constants.get(ConstantKey.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, Constants.ENCODING), writer);
        }
      }
    } catch (Exception ignore) {
      // ignore
    } finally {
      writer.close();
      reader.close();
    }
    // -----------------------------------------

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

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

  }
  /** {@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(Constants.get(ConstantKey.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(Constants.get(ConstantKey.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) && ENTITY_URI_PATTERN.matcher(href).matches()) {
            links.addLinks(title, href.substring(href.lastIndexOf('/') + 1));
          }
        }
      }
    } catch (Exception ignore) {
      // ignore
    } finally {
      reader.close();
    }

    return links;
  }
  @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(Constants.get(ConstantKey.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(Constants.get(ConstantKey.LINK)),
                pos,
                2,
                2);

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

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

    return res;
  }