/*
 * 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;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.mail.Header;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.cxf.interceptor.InInterceptors;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.jaxrs.ext.multipart.Attachment;
import org.apache.cxf.jaxrs.ext.multipart.Multipart;
import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
import org.apache.olingo.client.api.data.ResWrap;
import org.apache.olingo.client.api.serialization.ODataDeserializer;
import org.apache.olingo.client.api.serialization.ODataSerializer;
import org.apache.olingo.client.core.serialization.AtomSerializer;
import org.apache.olingo.client.core.serialization.JsonDeserializer;
import org.apache.olingo.client.core.serialization.JsonSerializer;
import org.apache.olingo.commons.api.data.ComplexValue;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.EntityCollection;
import org.apache.olingo.commons.api.data.Link;
import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.data.ValueType;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.core.edm.EdmTypeInfo;
import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory;
import org.apache.olingo.fit.metadata.EntityType;
import org.apache.olingo.fit.metadata.Metadata;
import org.apache.olingo.fit.metadata.NavigationProperty;
import org.apache.olingo.fit.methods.PATCH;
import org.apache.olingo.fit.rest.ResolvingReferencesInterceptor;
import org.apache.olingo.fit.rest.XHTTPMethodInterceptor;
import org.apache.olingo.fit.serializer.FITAtomDeserializer;
import org.apache.olingo.fit.utils.AbstractUtilities;
import org.apache.olingo.fit.utils.Accept;
import org.apache.olingo.fit.utils.Commons;
import org.apache.olingo.fit.utils.ConstantKey;
import org.apache.olingo.fit.utils.Constants;
import org.apache.olingo.fit.utils.FSManager;
import org.apache.olingo.fit.utils.JSONUtilities;
import org.apache.olingo.fit.utils.LinkInfo;
import org.apache.olingo.fit.utils.XMLUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

@Service
@Path("/V40/Static.svc")
@InInterceptors(classes = { XHTTPMethodInterceptor.class, ResolvingReferencesInterceptor.class })
public class Services {

  /**
   * Logger.
   */
  protected static final Logger LOG = LoggerFactory.getLogger(Services.class);

  private static final Pattern REQUEST_PATTERN = Pattern.compile("(.*) (http://.*) HTTP/.*");
  private static final Pattern BATCH_REQUEST_REF_PATTERN = Pattern.compile("(.*) ([$]\\d+)(.*) HTTP/.*");
  private static final Pattern REF_PATTERN = Pattern.compile("([$]\\d+)");
  private static final Pattern RELENTITY_SELECT_PATTERN = Pattern.compile("^.*\\(\\$select=.*\\)$");
  private static final Pattern CROSSJOIN_PATTERN = Pattern.compile(
      "^\\$crossjoin\\(.*\\)\\?\\$filter=\\([a-zA-Z/]+ eq [a-zA-Z/]+\\)$");
  protected static final String BOUNDARY = "batch_243234_25424_ef_892u748";
  protected static final String MULTIPART_MIXED = "multipart/mixed";
  protected static final String APPLICATION_OCTET_STREAM = "application/octet-stream";

  private final Map<String, String> providedAsync = new HashMap<String, String>();

  protected final ODataDeserializer atomDeserializer = new FITAtomDeserializer();
  protected final ODataDeserializer jsonDeserializer = new JsonDeserializer(true);
  protected final ODataSerializer atomSerializer = new AtomSerializer(true);
  protected final ODataSerializer jsonSerializer = new JsonSerializer(true, ContentType.JSON_FULL_METADATA);

  protected final Metadata metadata;
  protected final XMLUtilities xml;
  protected final JSONUtilities json;

  public Services() throws IOException {
    this(Commons.getMetadata());
  }

  protected Services(final Metadata metadata) throws IOException {
    this.metadata = metadata;
    xml = new XMLUtilities(metadata);
    json = new JSONUtilities(metadata);
  }

  /**
   * Provide sample services.
   *
   * @param accept Accept header.
   * @return OData services.
   */
  @GET
  public Response getServices(@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept) {
    try {
      final Accept acceptType = Accept.parse(accept);

      if (acceptType == Accept.ATOM) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      return xml.createResponse(
          null,
          FSManager.instance().readFile(Constants.get(ConstantKey.SERVICES), acceptType),
          null, acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  /**
   * Provide sample getMetadata().
   *
   * @return getMetadata().
   */
  @GET
  @Path("/$metadata")
  @Produces(MediaType.APPLICATION_XML)
  public Response getMetadata() {
    return getMetadata(Constants.get(ConstantKey.METADATA));
  }

  protected Response getMetadata(final String filename) {
    try {
      return xml.createResponse(null, FSManager.instance().readRes(filename, Accept.XML), null, Accept.XML);
    } catch (Exception e) {
      return xml.createFaultResponse(Accept.XML.toString(), e);
    }
  }

  @GET
  @Path("/redirect/{name}({id})")
  public Response conformanceRedirect(@Context final UriInfo uriInfo) {
    return Response.temporaryRedirect(
        URI.create(uriInfo.getRequestUri().toASCIIString().replace("/redirect", ""))).build();
  }

  @GET
  @Path("/$crossjoin({elements:.*})")
  public Response crossjoin(
      @PathParam("elements") final String elements,
      @QueryParam("$filter") final String filter) {

    try {
      if (CROSSJOIN_PATTERN.matcher("$crossjoin(" + elements + ")?$filter=" + filter).matches()) {
        final InputStream feed = FSManager.instance().readFile("crossjoin", Accept.JSON);

        return xml.createResponse(feed, null, Accept.JSON_FULLMETA);
      } else {
        throw new IOException("Unexpected crossjoin pattern");
      }
    } catch (Exception e) {
      return xml.createFaultResponse(Accept.JSON.toString(), e);
    }
  }

  @GET
  @Path("/relatedEntitySelect/{path:.*}")
  public Response relatedEntitySelect(@QueryParam("$expand") final String expand) {

    if (RELENTITY_SELECT_PATTERN.matcher(expand).matches()) {
      return xml.createResponse(null, null, Accept.JSON_FULLMETA);
    } else {
      return xml.createFaultResponse(Accept.JSON.toString(), new Exception("Unexpected expand pattern"));
    }
  }

  @DELETE
  @Path("/monitor/{name}")
  public Response removeMonitor(@PathParam("name") final String name) {
    providedAsync.remove(name);
    return xml.createResponse(null, null, null, Status.NO_CONTENT);
  }

  @GET
  @Path("/monitor/{name}")
  public Response async(@PathParam("name") final String name) {
    try {
      if (!providedAsync.containsKey(name)) {
        throw new NotFoundException();
      }
      final InputStream res = IOUtils.toInputStream(providedAsync.get(name), Constants.ENCODING);
      providedAsync.remove(name);
      return xml.createMonitorResponse(res);
    } catch (Exception e) {
      return xml.createFaultResponse(Accept.JSON_FULLMETA.toString(), e);
    }
  }

  @PUT
  @Path("/People(1)/Parent")
  public Response changeSingleValuedNavigationPropertyReference(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      final String content) {

    try {
      final Accept contentTypeValue = Accept.parse(contentType);
      assert contentTypeValue == Accept.JSON;

      jsonDeserializer.toEntity(IOUtils.toInputStream(content, Constants.ENCODING));

      return Response.noContent().type(MediaType.APPLICATION_JSON).build();
    } catch (Exception e) {
      LOG.error("While update single property reference", e);
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/$batch")
  @Consumes(MULTIPART_MIXED)
  @Produces(APPLICATION_OCTET_STREAM + ";boundary=" + BOUNDARY)
  public Response batch(
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      final @Multipart MultipartBody attachment) {
    try {
      final boolean continueOnError = prefer.contains("odata.continue-on-error");
      return xml.createBatchResponse(
          exploreMultipart(attachment.getAllAttachments(), BOUNDARY, continueOnError));
    } catch (IOException e) {
      return xml.createFaultResponse(Accept.XML.toString(), e);
    }
  }

  // ----------------------------------------------
  // just for non nullable property test into PropertyTestITCase
  // ----------------------------------------------
  @PATCH
  @Path("/Driver('2')")
  public Response patchDriver() {
    return xml.createFaultResponse(Accept.JSON_FULLMETA.toString(), new Exception("Non nullable properties"));
  }

  @GET
  @Path("/StoredPIs(1000)")
  public Response getStoredPI(@Context final UriInfo uriInfo) {
    final Entity entity = new Entity();
    entity.setType("Microsoft.Test.OData.Services.ODataWCFService.StoredPI");
    final Property id = new Property();
    id.setType("Edm.Int32");
    id.setName("StoredPIID");
    id.setValue(ValueType.PRIMITIVE, 1000);
    entity.getProperties().add(id);
    final Link edit = new Link();
    edit.setHref(uriInfo.getRequestUri().toASCIIString());
    edit.setRel("edit");
    edit.setTitle("StoredPI");
    entity.setEditLink(edit);

    final ByteArrayOutputStream content = new ByteArrayOutputStream();
    final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);
    try {
      jsonSerializer.write(writer, new ResWrap<Entity>((URI) null, null, entity));
      return xml.createResponse(new ByteArrayInputStream(content.toByteArray()), null, Accept.JSON_FULLMETA);
    } catch (Exception e) {
      LOG.error("While creating StoredPI", e);
      return xml.createFaultResponse(Accept.JSON_FULLMETA.toString(), e);
    }
  }

  @PATCH
  @Path("/StoredPIs(1000)")
  public Response patchStoredPI() {
    // just for non nullable property test into PropertyTestITCase
    return xml.createFaultResponse(Accept.JSON_FULLMETA.toString(), new Exception("Non nullable properties"));
  }

  @POST
  @Path("/async/$batch")
  public Response async(@Context final UriInfo uriInfo) {

    try {
      final ByteArrayOutputStream bos = new ByteArrayOutputStream();
      bos.write("HTTP/1.1 200 Ok".getBytes());
      bos.write(Constants.CRLF);
      bos.write("OData-Version: 4.0".getBytes());
      bos.write(Constants.CRLF);
      bos.write(("Content-Type: " + ContentType.APPLICATION_OCTET_STREAM + ";boundary=" + BOUNDARY).getBytes());
      bos.write(Constants.CRLF);
      bos.write(Constants.CRLF);

      bos.write(("--" + BOUNDARY).getBytes());
      bos.write(Constants.CRLF);
      bos.write("Content-Type: application/http".getBytes());
      bos.write(Constants.CRLF);
      bos.write("Content-Transfer-Encoding: binary".getBytes());
      bos.write(Constants.CRLF);
      bos.write(Constants.CRLF);

      bos.write("HTTP/1.1 202 Accepted".getBytes());
      bos.write(Constants.CRLF);
      bos.write("Location: http://service-root/async-monitor".getBytes());
      bos.write(Constants.CRLF);
      bos.write("Retry-After: 10".getBytes());
      bos.write(Constants.CRLF);
      bos.write(Constants.CRLF);
      bos.write(("--" + BOUNDARY + "--").getBytes());
      bos.write(Constants.CRLF);

      final UUID uuid = UUID.randomUUID();
      providedAsync.put(uuid.toString(), bos.toString(Constants.ENCODING.toString()));

      bos.flush();
      bos.close();

      return xml.createAsyncResponse(
          uriInfo.getRequestUri().toASCIIString().replace("async/$batch", "") + "monitor/" + uuid.toString());
    } catch (Exception e) {
      return xml.createFaultResponse(Accept.JSON.toString(), e);
    }
  }

  @GET
  @Path("/async/{name}")
  public Response async(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("name") final String name) {

    try {
      final Accept acceptType = Accept.parse(accept);
      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final String basePath = name + File.separatorChar;
      final StringBuilder path = new StringBuilder(basePath);

      path.append(metadata.getEntitySet(name).isSingleton()
          ? Constants.get(ConstantKey.ENTITY)
          : Constants.get(ConstantKey.FEED));

      final InputStream feed = FSManager.instance().readFile(path.toString(), acceptType);

      final StringBuilder builder = new StringBuilder();
      builder.append("HTTP/1.1 200 Ok").append(new String(Constants.CRLF));
      builder.append("Content-Type: ").append(accept)
          .append(new String(Constants.CRLF))
          .append(new String(Constants.CRLF));
      builder.append(IOUtils.toString(feed));
      IOUtils.closeQuietly(feed);

      final UUID uuid = UUID.randomUUID();
      providedAsync.put(uuid.toString(), builder.toString());

      return xml.createAsyncResponse(
          uriInfo.getRequestUri().toASCIIString().replaceAll("async/" + name, "") + "monitor/" + uuid.toString());
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  private void setInlineCount(final EntityCollection entitySet, final String count) {
    if ("true".equals(count)) {
      entitySet.setCount(entitySet.getEntities().size());
    }
  }

  private Response bodyPartRequest(final MimeBodyPart body, final Map<String, String> references) throws Exception {
    @SuppressWarnings("unchecked")
    final Enumeration<Header> en = body.getAllHeaders();

    Header header = en.nextElement();
    final String request =
        header.getName() + (StringUtils.isNotBlank(header.getValue()) ? ":" + header.getValue() : "");

    final Matcher matcher = REQUEST_PATTERN.matcher(request);
    final Matcher matcherRef = BATCH_REQUEST_REF_PATTERN.matcher(request);

    final MultivaluedMap<String, String> headers = new MultivaluedHashMap<String, String>();

    while (en.hasMoreElements()) {
      header = en.nextElement();
      headers.putSingle(header.getName(), header.getValue());
    }

    final Response res;
    final String url;
    final String method;

    if (matcher.find()) {
      url = matcher.group(2);
      method = matcher.group(1);
    } else if (matcherRef.find()) {
      url = references.get(matcherRef.group(2)) + matcherRef.group(3);
      method = matcherRef.group(1);
    } else {
      url = null;
      method = null;
    }

    if (url == null) {
      res = null;
    } else {
      final WebClient client = WebClient.create(url, "odatajclient", "odatajclient", null);
      client.headers(headers);

      if ("DELETE".equals(method)) {
        res = client.delete();
      } else {
        final InputStream is = body.getDataHandler().getInputStream();
        String content = IOUtils.toString(is);
        IOUtils.closeQuietly(is);

        final Matcher refs = REF_PATTERN.matcher(content);

        while (refs.find()) {
          content = content.replace(refs.group(1), references.get(refs.group(1)));
        }

        if ("PATCH".equals(method) || "MERGE".equals(method)) {
          client.header("X-HTTP-METHOD", method);
          res = client.invoke("POST", IOUtils.toInputStream(content));
        } else {
          res = client.invoke(method, IOUtils.toInputStream(content));
        }
      }

      // When updating to CXF 3.0.1, uncomment the following line, see CXF-5865
      // client.close();
    }

    return res;
  }

  private InputStream exploreMultipart(
      final List<Attachment> attachments, final String boundary, final boolean continueOnError)
      throws IOException {

    final ByteArrayOutputStream bos = new ByteArrayOutputStream();

    Response res = null;
    boolean goon = true;
    for (int i = 0; i < attachments.size() && goon; i++) {
      try {
        final Attachment obj = attachments.get(i);
        bos.write(("--" + boundary).getBytes());
        bos.write(Constants.CRLF);

        final Object content = obj.getDataHandler().getContent();
        if (content instanceof MimeMultipart) {
          final ByteArrayOutputStream chbos = new ByteArrayOutputStream();
          String lastContebtID = null;
          try {
            final Map<String, String> references = new HashMap<String, String>();

            final String cboundary = "changeset_" + UUID.randomUUID().toString();
            chbos.write(("Content-Type: multipart/mixed;boundary=" + cboundary).getBytes());
            chbos.write(Constants.CRLF);
            chbos.write(Constants.CRLF);

            for (int j = 0; j < ((MimeMultipart) content).getCount(); j++) {
              final MimeBodyPart part = (MimeBodyPart) ((MimeMultipart) content).getBodyPart(j);
              lastContebtID = part.getContentID();
              addChangesetItemIntro(chbos, lastContebtID, cboundary);

              res = bodyPartRequest(new MimeBodyPart(part.getInputStream()), references);
              if (!continueOnError && (res == null || res.getStatus() >= 400)) {
                throw new Exception("Failure processing changeset");
              }

              addSingleBatchResponse(res, lastContebtID, chbos);
              references.put("$" + lastContebtID, res.getHeaderString("Location"));
            }

            chbos.write(("--" + cboundary + "--").getBytes());
            chbos.write(Constants.CRLF);

            bos.write(chbos.toByteArray());
            IOUtils.closeQuietly(chbos);
          } catch (Exception e) {
            LOG.warn("While processing changeset", e);
            IOUtils.closeQuietly(chbos);

            addItemIntro(bos, lastContebtID);

            if (res == null || res.getStatus() < 400) {
              addErrorBatchResponse(e, "1", bos);
            } else {
              addSingleBatchResponse(res, lastContebtID, bos);
            }

            goon = continueOnError;
          }
        } else {
          addItemIntro(bos, null);

          res = bodyPartRequest(new MimeBodyPart(obj.getDataHandler().getInputStream()),
              Collections.<String, String> emptyMap());

          if (res.getStatus() >= 400) {
            goon = continueOnError;
            throw new Exception("Failure processing batch item");
          }

          addSingleBatchResponse(res, bos);
        }
      } catch (Exception e) {
        if (res == null || res.getStatus() < 400) {
          addErrorBatchResponse(e, bos);
        } else {
          addSingleBatchResponse(res, bos);
        }
      }
    }

    bos.write(("--" + boundary + "--").getBytes());

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

  private void addItemIntro(final ByteArrayOutputStream bos, final String contentId) throws IOException {
    bos.write("Content-Type: application/http".getBytes());
    bos.write(Constants.CRLF);
    bos.write("Content-Transfer-Encoding: binary".getBytes());
    bos.write(Constants.CRLF);

    if (StringUtils.isNotBlank(contentId)) {
      bos.write(("Content-ID: " + contentId).getBytes());
      bos.write(Constants.CRLF);
    }

    bos.write(Constants.CRLF);
  }

  private void addChangesetItemIntro(
      final ByteArrayOutputStream bos, final String contentId, final String cboundary) throws IOException {
    bos.write(("--" + cboundary).getBytes());
    bos.write(Constants.CRLF);
    bos.write(("Content-ID: " + contentId).getBytes());
    bos.write(Constants.CRLF);
    addItemIntro(bos, null);
  }

  private void addSingleBatchResponse(
      final Response response, final ByteArrayOutputStream bos) throws IOException {
    addSingleBatchResponse(response, null, bos);
  }

  private void addSingleBatchResponse(
      final Response response, final String contentId, final ByteArrayOutputStream bos) throws IOException {
    bos.write("HTTP/1.1 ".getBytes());
    bos.write(String.valueOf(response.getStatusInfo().getStatusCode()).getBytes());
    bos.write(" ".getBytes());
    bos.write(response.getStatusInfo().getReasonPhrase().getBytes());
    bos.write(Constants.CRLF);

    for (Map.Entry<String, List<Object>> header : response.getHeaders().entrySet()) {
      final StringBuilder builder = new StringBuilder();
      for (Object value : header.getValue()) {
        if (builder.length() > 0) {
          builder.append(", ");
        }
        builder.append(value.toString());
      }
      builder.insert(0, ": ").insert(0, header.getKey());
      bos.write(builder.toString().getBytes());
      bos.write(Constants.CRLF);
    }

    if (StringUtils.isNotBlank(contentId)) {
      bos.write(("Content-ID: " + contentId).getBytes());
      bos.write(Constants.CRLF);
    }

    bos.write(Constants.CRLF);

    final Object entity = response.getEntity();
    if (entity != null) {
      bos.write(IOUtils.toByteArray((InputStream) entity));
      bos.write(Constants.CRLF);
    }

    bos.write(Constants.CRLF);
  }

  protected void addErrorBatchResponse(final Exception e, final ByteArrayOutputStream bos)
      throws IOException {
    addErrorBatchResponse(e, null, bos);
  }

  protected void addErrorBatchResponse(final Exception e, final String contentId, final ByteArrayOutputStream bos)
      throws IOException {
    addSingleBatchResponse(xml.createFaultResponse(Accept.XML.toString(), e), contentId, bos);
  }

  /**
   * Retrieve entities from the given entity set and the given type.
   *
   * @param accept Accept header.
   * @param name entity set.
   * @param type entity type.
   * @return entity set.
   */
  @GET
  @Path("/{name}/{type:[a-zA-Z].*}")
  public Response getEntitySet(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("name") final String name,
      @PathParam("type") final String type) {

    try {
      final Accept acceptType = Accept.parse(accept);
      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final String basePath = name + File.separatorChar;
      final StringBuilder path = new StringBuilder(name).
          append(File.separatorChar).append(type).
          append(File.separatorChar);

      path.append(metadata.getEntitySet(name).isSingleton()
          ? Constants.get(ConstantKey.ENTITY)
              : Constants.get(ConstantKey.FEED));

      final InputStream feed = FSManager.instance().readFile(path.toString(), acceptType);
      return xml.createResponse(null, feed, Commons.getETag(basePath), acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @GET
  @Path("/{name}/{type:[a-zA-Z].*}")
  public Response getEntitySet(@Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("name") final String name,
      @QueryParam("$top") @DefaultValue(StringUtils.EMPTY) final String top,
      @QueryParam("$skip") @DefaultValue(StringUtils.EMPTY) final String skip,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      @QueryParam("$count") @DefaultValue(StringUtils.EMPTY) final String count,
      @QueryParam("$filter") @DefaultValue(StringUtils.EMPTY) final String filter,
      @QueryParam("$orderby") @DefaultValue(StringUtils.EMPTY) final String orderby,
      @QueryParam("$skiptoken") @DefaultValue(StringUtils.EMPTY) final String skiptoken,
      @PathParam("type") final String type) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final String location = uriInfo.getRequestUri().toASCIIString();
      try {
        // search for function ...
        final InputStream func = FSManager.instance().readFile(name, acceptType);
        return xml.createResponse(location, func, null, acceptType);
      } catch (NotFoundException e) {
        if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
          throw new UnsupportedMediaTypeException("Unsupported media type");
        }

        // search for entitySet ...
        final String basePath = name + File.separatorChar;

        final StringBuilder builder = new StringBuilder();
        builder.append(basePath);

        if (type != null) {
          builder.append(type).append(File.separatorChar);
        }

        if (StringUtils.isNotBlank(orderby)) {
          builder.append(Constants.get(ConstantKey.ORDERBY)).append(File.separatorChar).
          append(orderby).append(File.separatorChar);
        }

        if (StringUtils.isNotBlank(filter)) {
          builder.append(Constants.get(ConstantKey.FILTER)).append(File.separatorChar).
          append(filter.replaceAll("/", "."));
        } else if (StringUtils.isNotBlank(skiptoken)) {
          builder.append(Constants.get(ConstantKey.SKIP_TOKEN)).append(File.separatorChar).
          append(skiptoken);
        } else {
          builder.append(metadata.getEntitySet(name).isSingleton()
              ? Constants.get(ConstantKey.ENTITY)
                  : Constants.get(ConstantKey.FEED));
        }

        final InputStream feed = FSManager.instance().readFile(builder.toString(), Accept.ATOM);

        final ResWrap<EntityCollection> container = atomDeserializer.toEntitySet(feed);

        setInlineCount(container.getPayload(), count);

        final ByteArrayOutputStream content = new ByteArrayOutputStream();
        final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);

        // -----------------------------------------------
        // Evaluate $skip and $top
        // -----------------------------------------------
        List<Entity> entries = new ArrayList<Entity>(container.getPayload().getEntities());

        if (StringUtils.isNotBlank(skip)) {
          entries = entries.subList(Integer.valueOf(skip), entries.size());
        }

        if (StringUtils.isNotBlank(top)) {
          entries = entries.subList(0, Integer.valueOf(top));
        }

        container.getPayload().getEntities().clear();
        container.getPayload().getEntities().addAll(entries);
        // -----------------------------------------------

        if (acceptType == Accept.ATOM) {
          atomSerializer.write(writer, container);
        } else {
          jsonSerializer.write(writer, container);
        }
        return xml.createResponse(
            location,
            new ByteArrayInputStream(content.toByteArray()),
            Commons.getETag(basePath),
            acceptType);
      }
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  /**
   * Retrieve entity set or function execution sample.
   *
   * @param accept Accept header.
   * @param name entity set or function name.
   * @param format format query option.
   * @param count count query option.
   * @param filter filter query option.
   * @param orderby orderby query option.
   * @param skiptoken skiptoken query option.
   * @return entity set or function result.
   */
  @GET
  @Path("/{name}")
  public Response getEntitySet(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("name") final String name,
      @QueryParam("$top") @DefaultValue(StringUtils.EMPTY) final String top,
      @QueryParam("$skip") @DefaultValue(StringUtils.EMPTY) final String skip,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      @QueryParam("$count") @DefaultValue(StringUtils.EMPTY) final String count,
      @QueryParam("$filter") @DefaultValue(StringUtils.EMPTY) final String filter,
      @QueryParam("$orderby") @DefaultValue(StringUtils.EMPTY) final String orderby,
      @QueryParam("$skiptoken") @DefaultValue(StringUtils.EMPTY) final String skiptoken) {

    return getEntitySet(uriInfo, accept, name, top, skip, format, count, filter, orderby, skiptoken, null);
  }

  @GET
  @Path("/Person({entityId})")
  public Response getPerson(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entityId") final String entityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format);

    final Response internal = getEntityInternal(
        uriInfo.getRequestUri().toASCIIString(), accept, "Person", entityId, format, null, null);
    if (internal.getStatus() == 200) {
      InputStream entity = (InputStream) internal.getEntity();
      try {
        if (utils.getKey() == Accept.JSON_FULLMETA || utils.getKey() == Accept.ATOM) {
          entity = utils.getValue().addOperation(entity, "Sack", "#DefaultContainer.Sack",
              uriInfo.getAbsolutePath().toASCIIString()
              + "/Microsoft.Test.OData.Services.AstoriaDefaultService.SpecialEmployee/Sack");
        }

        return utils.getValue().createResponse(
            uriInfo.getRequestUri().toASCIIString(),
            entity,
            internal.getHeaderString("ETag"),
            utils.getKey());
      } catch (Exception e) {
        LOG.error("Error retrieving entity", e);
        return xml.createFaultResponse(accept, e);
      }
    } else {
      return internal;
    }
  }

  @GET
  @Path("/Product({entityId})")
  public Response getProduct(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entityId") final String entityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format);

    final Response internal = getEntityInternal(
        uriInfo.getRequestUri().toASCIIString(), accept, "Product", entityId, format, null, null);
    if (internal.getStatus() == 200) {
      InputStream entity = (InputStream) internal.getEntity();
      try {
        if (utils.getKey() == Accept.JSON_FULLMETA || utils.getKey() == Accept.ATOM) {
          entity = utils.getValue().addOperation(entity,
              "ChangeProductDimensions", "#DefaultContainer.ChangeProductDimensions",
              uriInfo.getAbsolutePath().toASCIIString() + "/ChangeProductDimensions");
        }

        return utils.getValue().createResponse(
            uriInfo.getRequestUri().toASCIIString(),
            entity,
            internal.getHeaderString("ETag"),
            utils.getKey());
      } catch (Exception e) {
        LOG.error("Error retrieving entity", e);
        return xml.createFaultResponse(accept, e);
      }
    } else {
      return internal;
    }
  }

  @GET
  @Path("/ComputerDetail({entityId})")
  public Response getComputerDetail(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entityId") final String entityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format);

    final Response internal = getEntityInternal(
        uriInfo.getRequestUri().toASCIIString(), accept, "ComputerDetail", entityId, format, null, null);
    if (internal.getStatus() == 200) {
      InputStream entity = (InputStream) internal.getEntity();
      try {
        if (utils.getKey() == Accept.JSON_FULLMETA || utils.getKey() == Accept.ATOM) {
          entity = utils.getValue().addOperation(entity,
              "ResetComputerDetailsSpecifications", "#DefaultContainer.ResetComputerDetailsSpecifications",
              uriInfo.getAbsolutePath().toASCIIString() + "/ResetComputerDetailsSpecifications");
        }

        return utils.getValue().createResponse(
            uriInfo.getRequestUri().toASCIIString(),
            entity,
            internal.getHeaderString("ETag"),
            utils.getKey());
      } catch (Exception e) {
        LOG.error("Error retrieving entity", e);
        return xml.createFaultResponse(accept, e);
      }
    } else {
      return internal;
    }
  }

  /**
   * Retrieve entity sample.
   *
   * @param accept Accept header.
   * @param entitySetName Entity set name.
   * @param entityId entity id.
   * @param format format query option.
   * @param expand expand query option.
   * @param select select query option.
   * @return entity.
   */
  @GET
  @Path("/{entitySetName}({entityId})")
  public Response getEntity(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      @QueryParam("$expand") @DefaultValue(StringUtils.EMPTY) final String expand,
      @QueryParam("$select") @DefaultValue(StringUtils.EMPTY) final String select) {

    return getEntityInternal(
        uriInfo.getRequestUri().toASCIIString(), accept, entitySetName, entityId, format, expand, select);
  }

  protected Response getEntityInternal(
      final String location,
      final String accept,
      final String entitySetName,
      final String entityId,
      final String format,
      final String expand,
      final String select) {

    try {
      final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format);

      if (utils.getKey() == Accept.XML || utils.getKey() == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final Map.Entry<String, InputStream> entityInfo =
          utils.getValue().readEntity(entitySetName, entityId, Accept.ATOM);

      final InputStream entity = entityInfo.getValue();

      ResWrap<Entity> container = atomDeserializer.toEntity(entity);
      if (container.getContextURL() == null) {
        container = new ResWrap<Entity>(URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX)
            + entitySetName + Constants.get(ConstantKey.ODATA_METADATA_ENTITY_SUFFIX)),
            container.getMetadataETag(), container.getPayload());
      }
      final Entity entry = container.getPayload();

      if ((this instanceof KeyAsSegment)) {
        final Link editLink = new Link();
        editLink.setRel("edit");
        editLink.setTitle(entitySetName);
        editLink.setHref(Constants.get(ConstantKey.DEFAULT_SERVICE_URL) + entitySetName + "/" + entityId);

        entry.setEditLink(editLink);
      }

      if (StringUtils.isNotBlank(select)) {
        final List<String> properties = Arrays.asList(select.split(","));
        final Set<Property> toBeRemoved = new HashSet<Property>();

        for (Property property : entry.getProperties()) {
          if (!properties.contains(property.getName())) {
            toBeRemoved.add(property);
          }
        }

        entry.getProperties().removeAll(toBeRemoved);

        final Set<Link> linkToBeRemoved = new HashSet<Link>();

        for (Link link : entry.getNavigationLinks()) {
          if (!properties.contains(link.getTitle().replaceAll("@.*$", "")) && !properties.contains(link.getTitle())) {
            linkToBeRemoved.add(link);
          }
        }

        entry.getNavigationLinks().removeAll(linkToBeRemoved);
      }

      String tempExpand = expand;
      if (StringUtils.isNotBlank(tempExpand)) {
        tempExpand = StringUtils.substringBefore(tempExpand, "(");
        final List<String> links = Arrays.asList(tempExpand.split(","));

        final Map<Link, Link> replace = new HashMap<Link, Link>();

        for (Link link : entry.getNavigationLinks()) {
          if (links.contains(link.getTitle())) {
            // expand link
            final Link rep = new Link();
            rep.setHref(link.getHref());
            rep.setRel(link.getRel());
            rep.setTitle(link.getTitle());
            rep.setType(link.getType());
            if (link.getType().equals(Constants.get(ConstantKey.ATOM_LINK_ENTRY))) {
              // inline entry
              final Entity inline = atomDeserializer.toEntity(
                  xml.expandEntity(entitySetName, entityId, link.getTitle())).getPayload();
              rep.setInlineEntity(inline);
            } else if (link.getType().equals(Constants.get(ConstantKey.ATOM_LINK_FEED))) {
              // inline feed
              final EntityCollection inline = atomDeserializer.toEntitySet(
                  xml.expandEntity(entitySetName, entityId, link.getTitle())).getPayload();
              rep.setInlineEntitySet(inline);
            }
            replace.put(link, rep);
          }
        }

        for (Map.Entry<Link, Link> link : replace.entrySet()) {
          entry.getNavigationLinks().remove(link.getKey());
          entry.getNavigationLinks().add(link.getValue());
        }
      }

      return xml.createResponse(
          location,
          xml.writeEntity(utils.getKey(), container),
          Commons.getETag(entityInfo.getKey()),
          utils.getKey());
    } catch (Exception e) {
      LOG.error("Error retrieving entity", e);
      return xml.createFaultResponse(accept, e);
    }
  }

  @GET
  @Path("/{entitySetName}({entityId})/$value")
  public Response getMediaEntity(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId) {

    try {
      if (!accept.contains("*/*") && !accept.contains("application/octet-stream")) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final AbstractUtilities utils = getUtilities(null);
      final Map.Entry<String, InputStream> entityInfo = utils.readMediaEntity(entitySetName, entityId);
      return utils.createResponse(
          uriInfo.getRequestUri().toASCIIString(),
          entityInfo.getValue(),
          Commons.getETag(entityInfo.getKey()),
          null);

    } catch (Exception e) {
      LOG.error("Error retrieving entity", e);
      return xml.createFaultResponse(accept, e);
    }
  }

  @GET
  @Path("/People/{type:.*}")
  public Response getPeople(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("type") final String type,
      @QueryParam("$top") @DefaultValue(StringUtils.EMPTY) final String top,
      @QueryParam("$skip") @DefaultValue(StringUtils.EMPTY) final String skip,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      @QueryParam("$count") @DefaultValue(StringUtils.EMPTY) final String count,
      @QueryParam("$filter") @DefaultValue(StringUtils.EMPTY) final String filter,
      @QueryParam("$search") @DefaultValue(StringUtils.EMPTY) final String search,
      @QueryParam("$orderby") @DefaultValue(StringUtils.EMPTY) final String orderby,
      @QueryParam("$skiptoken") @DefaultValue(StringUtils.EMPTY) final String skiptoken) {

    return StringUtils.isBlank(filter) && StringUtils.isBlank(search) ?
        NumberUtils.isNumber(type) ?
            getEntityInternal(uriInfo.getRequestUri().toASCIIString(), accept, "People", type, format, null, null) :
            getEntitySet(accept, "People", type) :
        getEntitySet(uriInfo, accept, "People", top, skip, format, count, filter, orderby, skiptoken, type);
  }

  @GET
  @Path("/Boss")
  public Response getSingletonBoss(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    return getEntityInternal(
        uriInfo.getRequestUri().toASCIIString(), accept, "Boss", StringUtils.EMPTY, format, null, null);
  }

  @GET
  @Path("/Company")
  public Response getSingletonCompany(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    return getEntityInternal(
        uriInfo.getRequestUri().toASCIIString(), accept, "Company", StringUtils.EMPTY, format, null, null);
  }

  @PATCH
  @Path("/Company")
  @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON })
  @Consumes({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON })
  public Response patchSingletonCompany(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      @HeaderParam("If-Match") @DefaultValue(StringUtils.EMPTY) final String ifMatch,
      final String changes) {

    return patchEntityInternal(uriInfo, accept, contentType, prefer, ifMatch, "Company", StringUtils.EMPTY, changes);
  }

  @GET
  @Path("/Customers")
  public Response getCustomers(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      @QueryParam("$deltatoken") @DefaultValue(StringUtils.EMPTY) final String deltatoken) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final InputStream output;
      if (StringUtils.isBlank(deltatoken)) {
        final InputStream input = (InputStream) getEntitySet(
            uriInfo, accept, "Customers", null, null, format, null, null, null, null).getEntity();
        final EntityCollection entitySet = xml.readEntitySet(acceptType, input);

        boolean trackChanges = prefer.contains("odata.track-changes");
        if (trackChanges) {
          entitySet.setDeltaLink(URI.create("Customers?$deltatoken=8015"));
        }

        output = xml.writeEntitySet(acceptType, new ResWrap<EntityCollection>(
            URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + "Customers"),
            null,
            entitySet));
      } else {
        output = FSManager.instance().readFile("delta", acceptType);
      }

      final Response response = xml.createResponse(
          null,
          output,
          null,
          acceptType);
      if (StringUtils.isNotBlank(prefer)) {
        response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer));
      }
      return response;
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/{entitySetName}")
  @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON })
  @Consumes({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM })
  public Response postNewEntity(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      @PathParam("entitySetName") final String entitySetName,
      final String entity) {

    try {
      final Accept acceptType = Accept.parse(accept);
      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final ResWrap<Entity> container;

      final org.apache.olingo.fit.metadata.EntitySet entitySet = metadata.getEntitySet(entitySetName);

      final Entity entry;
      final String entityKey;
      if (xml.isMediaContent(entitySetName)) {
        entry = new Entity();
        entry.setMediaContentType(ContentType.APPLICATION_OCTET_STREAM.toContentTypeString());
        entry.setType(entitySet.getType());

        entityKey = xml.getDefaultEntryKey(entitySetName, entry);

        xml.addMediaEntityValue(entitySetName, entityKey, IOUtils.toInputStream(entity, Constants.ENCODING));

        final Pair<String, EdmPrimitiveTypeKind> id = Commons.getMediaContent().get(entitySetName);
        if (id != null) {
          final Property prop = new Property();
          prop.setName(id.getKey());
          prop.setType(id.getValue().toString());
          prop.setValue(ValueType.PRIMITIVE,
              id.getValue() == EdmPrimitiveTypeKind.Int32
              ? Integer.parseInt(entityKey)
                  : id.getValue() == EdmPrimitiveTypeKind.Guid
                  ? UUID.fromString(entityKey)
                      : entityKey);
          entry.getProperties().add(prop);
        }

        final Link editLink = new Link();
        editLink.setHref(Commons.getEntityURI(entitySetName, entityKey));
        editLink.setRel("edit");
        editLink.setTitle(entitySetName);
        entry.setEditLink(editLink);

        entry.setMediaContentSource(URI.create(editLink.getHref() + "/$value"));

        container = new ResWrap<Entity>((URI) null, null, entry);
      } else {
        final Accept contentTypeValue = Accept.parse(contentType);
        if (Accept.ATOM == contentTypeValue) {
          container = atomDeserializer.toEntity(IOUtils.toInputStream(entity, Constants.ENCODING));
        } else {
          container = jsonDeserializer.toEntity(IOUtils.toInputStream(entity, Constants.ENCODING));
        }
        entry = container.getPayload();
        updateInlineEntities(entry);

        entityKey = xml.getDefaultEntryKey(entitySetName, entry);
      }

      normalizeAtomEntry(entry, entitySetName, entityKey);

      final ByteArrayOutputStream content = new ByteArrayOutputStream();
      final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);
      atomSerializer.write(writer, container);
      writer.flush();
      writer.close();

      final InputStream serialization =
          xml.addOrReplaceEntity(entityKey, entitySetName, new ByteArrayInputStream(content.toByteArray()), entry);

      ResWrap<Entity> result = atomDeserializer.toEntity(serialization);
      result = new ResWrap<Entity>(
          URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX)
              + entitySetName + Constants.get(ConstantKey.ODATA_METADATA_ENTITY_SUFFIX)),
              null, result.getPayload());

      final String path = Commons.getEntityBasePath(entitySetName, entityKey);
      FSManager.instance().putInMemory(result, path + Constants.get(ConstantKey.ENTITY));

      final String location;

      if ((this instanceof KeyAsSegment)) {
        location = uriInfo.getRequestUri().toASCIIString() + "/" + entityKey;

        final Link editLink = new Link();
        editLink.setRel("edit");
        editLink.setTitle(entitySetName);
        editLink.setHref(location);

        result.getPayload().setEditLink(editLink);
      } else {
        location = uriInfo.getRequestUri().toASCIIString() + "(" + entityKey + ")";
      }

      final Response response;
      if ("return-no-content".equalsIgnoreCase(prefer)) {
        response = xml.createResponse(
            location,
            null,
            null,
            acceptType,
            Response.Status.NO_CONTENT);
      } else {
        response = xml.createResponse(
            location,
            xml.writeEntity(acceptType, result),
            null,
            acceptType,
            Response.Status.CREATED);
      }

      if (StringUtils.isNotBlank(prefer)) {
        response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer));
      }

      return response;
    } catch (Exception e) {
      LOG.error("While creating new entity", e);
      return xml.createFaultResponse(accept, e);
    }
  }

  private void updateInlineEntities(final Entity entity) {
    final String type = entity.getType();
    EntityType entityType;
    Map<String, NavigationProperty> navProperties = Collections.emptyMap();
    if (type != null && type.length() > 0) {
      entityType = metadata.getEntityOrComplexType(type);
      navProperties = entityType.getNavigationPropertyMap();
    }

    for (Property property : entity.getProperties()) {
      if (navProperties.containsKey(property.getName())) {
        Link alink = new Link();
        alink.setTitle(property.getName());
        alink.getAnnotations().addAll(property.getAnnotations());

        alink.setType(navProperties.get(property.getName()).isEntitySet()
            ? Constants.get(ConstantKey.ATOM_LINK_FEED)
                : Constants.get(ConstantKey.ATOM_LINK_ENTRY));

        alink.setRel(Constants.get(ConstantKey.ATOM_LINK_REL) + property.getName());

        if (property.isCollection()) {
          EntityCollection inline = new EntityCollection();
          for (Object value : property.asCollection()) {
            Entity inlineEntity = new Entity();
            inlineEntity.setType(navProperties.get(property.getName()).getType());
            for (Property prop : ((ComplexValue) value).getValue()) {
              inlineEntity.getProperties().add(prop);
            }
            inline.getEntities().add(inlineEntity);
          }
          alink.setInlineEntitySet(inline);
        } else if (property.isComplex()) {
          Entity inline = new Entity();
          inline.setType(navProperties.get(property.getName()).getType());
          for (Property prop : property.asComplex().getValue()) {
            inline.getProperties().add(prop);
          }
          alink.setInlineEntity(inline);

        } else {
          throw new IllegalStateException("Invalid navigation property " + property);
        }
        entity.getNavigationLinks().add(alink);
      }
    }
  }

  private void normalizeAtomEntry(final Entity entry, final String entitySetName, final String entityKey) {
    final org.apache.olingo.fit.metadata.EntitySet entitySet = metadata.getEntitySet(entitySetName);
    final EntityType entityType = metadata.getEntityOrComplexType(entitySet.getType());
    for (Map.Entry<String, org.apache.olingo.fit.metadata.Property> property : entityType.getPropertyMap().entrySet()) {
      if (entry.getProperty(property.getKey()) == null && property.getValue().isNullable()) {
        final Property prop = new Property();
        prop.setName(property.getKey());
        prop.setValue(ValueType.PRIMITIVE, null);
        entry.getProperties().add(prop);
      }
    }

    for (Map.Entry<String, NavigationProperty> property : entityType.getNavigationPropertyMap().entrySet()) {
      boolean found = false;
      for (Link link : entry.getNavigationLinks()) {
        if (link.getTitle().equals(property.getKey())) {
          found = true;
        }
      }

      if (!found) {
        final Link link = new Link();
        link.setTitle(property.getKey());
        link.setType(property.getValue().isEntitySet()
            ? Constants.get(ConstantKey.ATOM_LINK_FEED)
                : Constants.get(ConstantKey.ATOM_LINK_ENTRY));
        link.setRel(Constants.get(ConstantKey.ATOM_LINK_REL) + property.getKey());
        link.setHref(entitySetName + "(" + entityKey + ")/" + property.getKey());
        entry.getNavigationLinks().add(link);
      }
    }
  }

  @GET
  @Path("/Company/Microsoft.Test.OData.Services.ODataWCFService.GetEmployeesCount{paren:[\\(\\)]*}")
  public Response functionGetEmployeesCount(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final Property property = new Property();
      property.setType("Edm.Int32");
      property.setValue(ValueType.PRIMITIVE, 2);
      final ResWrap<Property> container = new ResWrap<Property>(
          URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + property.getType()), null,
          property);

      return xml.createResponse(
          null,
          xml.writeProperty(acceptType, container),
          null,
          acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/Person({entityId})/{type:.*}/Sack")
  public Response actionSack(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entityId") final String entityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format);

    if (utils.getKey() == Accept.XML || utils.getKey() == Accept.TEXT) {
      throw new UnsupportedMediaTypeException("Unsupported media type");
    }

    try {
      final Map.Entry<String, InputStream> entityInfo = xml.readEntity("Person", entityId, Accept.ATOM);

      final InputStream entity = entityInfo.getValue();
      final ResWrap<Entity> container = atomDeserializer.toEntity(entity);

      container.getPayload().getProperty("Salary").setValue(ValueType.PRIMITIVE, 0);
      container.getPayload().getProperty("Title").setValue(ValueType.PRIMITIVE, "[Sacked]");

      final FSManager fsManager = FSManager.instance();
      fsManager.putInMemory(xml.writeEntity(Accept.ATOM, container),
          fsManager.getAbsolutePath(Commons.getEntityBasePath("Person", entityId) + Constants.get(
              ConstantKey.ENTITY), Accept.ATOM));

      return utils.getValue().createResponse(null, null, null, utils.getKey(), Response.Status.NO_CONTENT);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/Person/{type:.*}/IncreaseSalaries")
  public Response actionIncreaseSalaries(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("type") final String type,
      final String body) {

    final String name = "Person";
    try {
      final Accept acceptType = Accept.parse(accept);
      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final JsonNode tree = new ObjectMapper().readTree(body);
      if (!tree.has("n")) {
        throw new Exception("Missing parameter: n");
      }
      final int n = tree.get("n").asInt();

      final StringBuilder path = new StringBuilder(name).
          append(File.separatorChar).append(type).
          append(File.separatorChar);

      path.append(metadata.getEntitySet(name).isSingleton()
          ? Constants.get(ConstantKey.ENTITY)
              : Constants.get(ConstantKey.FEED));

      final InputStream feed = FSManager.instance().readFile(path.toString(), acceptType);

      final ByteArrayOutputStream copy = new ByteArrayOutputStream();
      IOUtils.copy(feed, copy);
      IOUtils.closeQuietly(feed);

      String newContent = new String(copy.toByteArray(), "UTF-8");
      final Pattern salary = Pattern.compile(acceptType == Accept.ATOM
          ? "\\<d:Salary m:type=\"Edm.Int32\"\\>(-?\\d+)\\</d:Salary\\>"
              : "\"Salary\":(-?\\d+),");
      final Matcher salaryMatcher = salary.matcher(newContent);
      while (salaryMatcher.find()) {
        final Long newSalary = Long.valueOf(salaryMatcher.group(1)) + n;
        newContent = newContent.
            replaceAll("\"Salary\":" + salaryMatcher.group(1) + ",",
                "\"Salary\":" + newSalary + ",").
                replaceAll("\\<d:Salary m:type=\"Edm.Int32\"\\>" + salaryMatcher.group(1) + "</d:Salary\\>",
                    "<d:Salary m:type=\"Edm.Int32\">" + newSalary + "</d:Salary>");
      }

      FSManager.instance().putInMemory(IOUtils.toInputStream(newContent, Constants.ENCODING),
          FSManager.instance().getAbsolutePath(path.toString(), acceptType));

      return xml.createResponse(null, null, null, acceptType, Response.Status.NO_CONTENT);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/Product({entityId})/ChangeProductDimensions")
  public Response actionChangeProductDimensions(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entityId") final String entityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String argument) {

    final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format);

    if (utils.getKey() == Accept.XML || utils.getKey() == Accept.TEXT) {
      throw new UnsupportedMediaTypeException("Unsupported media type");
    }

    try {
      final Map.Entry<String, InputStream> entityInfo = xml.readEntity("Product", entityId, Accept.ATOM);

      final InputStream entity = entityInfo.getValue();
      final ResWrap<Entity> container = atomDeserializer.toEntity(entity);

      final Entity param = xml.readEntity(utils.getKey(), IOUtils.toInputStream(argument, Constants.ENCODING));

      final Property property = param.getProperty("dimensions");
      container.getPayload().getProperty("Dimensions").setValue(property.getValueType(), property.getValue());

      final FSManager fsManager = FSManager.instance();
      fsManager.putInMemory(xml.writeEntity(Accept.ATOM, container),
          fsManager.getAbsolutePath(Commons.getEntityBasePath("Product", entityId) + Constants.get(
              ConstantKey.ENTITY), Accept.ATOM));

      return utils.getValue().createResponse(null, null, null, utils.getKey(), Response.Status.NO_CONTENT);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/ComputerDetail({entityId})/ResetComputerDetailsSpecifications")
  public Response actionResetComputerDetailsSpecifications(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entityId") final String entityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String argument) {

    final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format);

    if (utils.getKey() == Accept.XML || utils.getKey() == Accept.TEXT) {
      throw new UnsupportedMediaTypeException("Unsupported media type");
    }

    try {
      final Map.Entry<String, InputStream> entityInfo = xml.readEntity("ComputerDetail", entityId, Accept.ATOM);

      final InputStream entity = entityInfo.getValue();
      final ResWrap<Entity> container = atomDeserializer.toEntity(entity);

      final Entity param = xml.readEntity(utils.getKey(), IOUtils.toInputStream(argument, Constants.ENCODING));

      Property property = param.getProperty("specifications");
      container.getPayload().getProperty("SpecificationsBag").setValue(property.getValueType(), property.getValue());
      property = param.getProperty("purchaseTime");
      container.getPayload().getProperty("PurchaseDate").setValue(property.getValueType(), property.getValue());

      final FSManager fsManager = FSManager.instance();
      fsManager.putInMemory(xml.writeEntity(Accept.ATOM, container),
          fsManager.getAbsolutePath(Commons.getEntityBasePath("ComputerDetail", entityId) + Constants.get(
              ConstantKey.ENTITY), Accept.ATOM));

      return utils.getValue().createResponse(null, null, null, utils.getKey(), Response.Status.NO_CONTENT);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/Company/Microsoft.Test.OData.Services.ODataWCFService.IncreaseRevenue{paren:[\\(\\)]*}")
  public Response actionIncreaseRevenue(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String param) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final Accept contentTypeValue = Accept.parse(contentType);
      final Entity entry = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING));

      return xml.createResponse(
          null,
          xml.writeProperty(acceptType, entry.getProperty("IncreaseValue")),
          null,
          acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @GET
  @Path("/Products({entityId})/Microsoft.Test.OData.Services.ODataWCFService.GetProductDetails({param:.*})")
  public Response functionGetProductDetails(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entityId") final String entityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final Entity entry = new Entity();
      entry.setType("Microsoft.Test.OData.Services.ODataWCFService.ProductDetail");
      final Property productId = new Property();
      productId.setName("ProductID");
      productId.setType("Edm.Int32");
      productId.setValue(ValueType.PRIMITIVE, Integer.valueOf(entityId));
      entry.getProperties().add(productId);
      final Property productDetailId = new Property();
      productDetailId.setName("ProductDetailID");
      productDetailId.setType("Edm.Int32");
      productDetailId.setValue(ValueType.PRIMITIVE, 2);
      entry.getProperties().add(productDetailId);

      final Link link = new Link();
      link.setRel("edit");
      link.setHref(URI.create(
          Constants.get(ConstantKey.DEFAULT_SERVICE_URL)
              + "ProductDetails(ProductID=6,ProductDetailID=1)").toASCIIString());
      entry.setEditLink(link);

      final EntityCollection feed = new EntityCollection();
      feed.getEntities().add(entry);

      final ResWrap<EntityCollection> container = new ResWrap<EntityCollection>(
          URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + "ProductDetail"), null,
          feed);

      return xml.createResponse(
          null,
          xml.writeEntitySet(acceptType, container),
          null,
          acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/Products({entityId})/Microsoft.Test.OData.Services.ODataWCFService.AddAccessRight{paren:[\\(\\)]*}")
  public Response actionAddAccessRight(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String param) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final Accept contentTypeValue = Accept.parse(contentType);
      final Entity entry = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING));

      assert 1 == entry.getProperties().size();
      assert entry.getProperty("accessRight") != null;

      final Property property = entry.getProperty("accessRight");
      property.setType("Microsoft.Test.OData.Services.ODataWCFService.AccessLevel");

      final ResWrap<Property> result = new ResWrap<Property>(
          URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + property.getType()),
          null, property);

      return xml.createResponse(
          null,
          xml.writeProperty(acceptType, result),
          null,
          acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/Customers({personId})/Microsoft.Test.OData.Services.ODataWCFService.ResetAddress{paren:[\\(\\)]*}")
  public Response actionResetAddress(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("personId") final String personId,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String param) {

    try {
      final Accept contentTypeValue = Accept.parse(contentType);
      final Entity entry = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING));

      assert 2 == entry.getProperties().size();
      assert entry.getProperty("addresses") != null;
      assert entry.getProperty("index") != null;

      return getEntityInternal(
          uriInfo.getRequestUri().toASCIIString(), accept, "Customers", personId, format, null, null);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @GET
  @Path("/ProductDetails(ProductID={productId},ProductDetailID={productDetailId})"
      + "/Microsoft.Test.OData.Services.ODataWCFService.GetRelatedProduct{paren:[\\(\\)]*}")
  public Response functionGetRelatedProduct(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("productId") final String productId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    return getEntityInternal(
        uriInfo.getRequestUri().toASCIIString(), accept, "Products", productId, format, null, null);
  }

  @POST
  @Path("/Accounts({entityId})/Microsoft.Test.OData.Services.ODataWCFService.RefreshDefaultPI{paren:[\\(\\)]*}")
  public Response actionRefreshDefaultPI(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @PathParam("entityId") final String entityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String param) {

    try {
      final Accept contentTypeValue = Accept.parse(contentType);
      final Entity entry = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING));

      assert 1 == entry.getProperties().size();
      assert entry.getProperty("newDate") != null;

      return functionGetDefaultPI(accept, entityId, format);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @GET
  @Path("/Accounts({entityId})/Microsoft.Test.OData.Services.ODataWCFService.GetDefaultPI{paren:[\\(\\)]*}")
  public Response functionGetDefaultPI(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entityId") final String entityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    return getContainedEntity(accept, entityId, "MyPaymentInstruments", entityId + "901", format);
  }

  @GET
  @Path("/Accounts({entityId})/Microsoft.Test.OData.Services.ODataWCFService.GetAccountInfo{paren:[\\(\\)]*}")
  public Response functionGetAccountInfo(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entityId") final String entityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    return getPath(accept, "Accounts", entityId, "AccountInfo", format);
  }

  @GET
  @Path("/Accounts({entityId})/MyGiftCard/Microsoft.Test.OData.Services.ODataWCFService.GetActualAmount({param:.*})")
  public Response functionGetActualAmount(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final Property property = new Property();
      property.setType("Edm.Double");
      property.setValue(ValueType.PRIMITIVE, 41.79);

      final ResWrap<Property> container = new ResWrap<Property>((URI) null, null, property);

      return xml.createResponse(
          null,
          xml.writeProperty(acceptType, container),
          null,
          acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  /**
   * Retrieve entity reference sample.
   *
   * @param accept Accept header.
   * @param path path.
   * @param format format query option.
   * @return entity reference or feed of entity reference.
   */
  @GET
  @Path("/{path:.*}/$ref")
  public Response getEntityReference(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("path") final String path,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    try {
      final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format);

      if (utils.getKey() == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final String filename = Base64.encodeBase64String(path.getBytes("UTF-8"));

      return utils.getValue().createResponse(
          FSManager.instance().readFile(Constants.get(ConstantKey.REF)
              + File.separatorChar + filename, utils.getKey()),
          null,
          utils.getKey());
    } catch (Exception e) {
      LOG.error("Error retrieving entity", e);
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/People")
  @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON })
  @Consumes({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM })
  public Response postPeople(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      final String entity) {

    if ("{\"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.Person\"}".equals(entity)) {
      return xml.createFaultResponse(accept, new BadRequestException());
    }

    return postNewEntity(uriInfo, accept, contentType, prefer, "People", entity);
  }

  private Response patchEntityInternal(final UriInfo uriInfo,
      final String accept, final String contentType, final String prefer, final String ifMatch,
      final String entitySetName, final String entityId, final String changes) {

    try {
      final Accept acceptType = Accept.parse(accept);

      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final Map.Entry<String, InputStream> entityInfo = xml.readEntity(entitySetName, entityId, Accept.ATOM);

      final String etag = Commons.getETag(entityInfo.getKey());
      if (StringUtils.isNotBlank(ifMatch) && !ifMatch.equals(etag)) {
        throw new ConcurrentModificationException("Concurrent modification");
      }

      final Accept contentTypeValue = Accept.parse(contentType);

      final Entity entryChanges;

      if (contentTypeValue == Accept.XML || contentTypeValue == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      } else if (contentTypeValue == Accept.ATOM) {
        entryChanges = atomDeserializer.toEntity(
            IOUtils.toInputStream(changes, Constants.ENCODING)).getPayload();
      } else {
        final ResWrap<Entity> jcont = jsonDeserializer.toEntity(IOUtils.toInputStream(changes, Constants.ENCODING));
        entryChanges = jcont.getPayload();
      }

      final ResWrap<Entity> container = atomDeserializer.toEntity(entityInfo.getValue());

      for (Property property : entryChanges.getProperties()) {
        final Property _property = container.getPayload().getProperty(property.getName());
        if (_property == null) {
          container.getPayload().getProperties().add(property);
        } else {
          _property.setValue(property.getValueType(), property.getValue());
        }
      }

      for (Link link : entryChanges.getNavigationLinks()) {
        container.getPayload().getNavigationLinks().add(link);
      }

      final ByteArrayOutputStream content = new ByteArrayOutputStream();
      final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);
      atomSerializer.write(writer, container);
      writer.flush();
      writer.close();

      final InputStream res = xml.addOrReplaceEntity(
          entityId, entitySetName, new ByteArrayInputStream(content.toByteArray()), container.getPayload());

      final ResWrap<Entity> cres = atomDeserializer.toEntity(res);

      normalizeAtomEntry(cres.getPayload(), entitySetName, entityId);

      final String path = Commons.getEntityBasePath(entitySetName, entityId);
      FSManager.instance().putInMemory(
          cres, path + File.separatorChar + Constants.get(ConstantKey.ENTITY));

      final Response response;
      if ("return-content".equalsIgnoreCase(prefer)) {
        response = xml.createResponse(
            uriInfo.getRequestUri().toASCIIString(),
            xml.readEntity(entitySetName, entityId, acceptType).getValue(),
            null, acceptType, Response.Status.OK);
      } else {
        res.close();
        response = xml.createResponse(
            uriInfo.getRequestUri().toASCIIString(),
            null,
            null,
            acceptType, Response.Status.NO_CONTENT);
      }

      if (StringUtils.isNotBlank(prefer)) {
        response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer));
      }

      return response;
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @PATCH
  @Path("/{entitySetName}({entityId})")
  @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON })
  @Consumes({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON })
  public Response patchEntity(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      @HeaderParam("If-Match") @DefaultValue(StringUtils.EMPTY) final String ifMatch,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId,
      final String changes) {

    final Response response =
        getEntityInternal(uriInfo.getRequestUri().toASCIIString(),
            accept, entitySetName, entityId, accept, StringUtils.EMPTY, StringUtils.EMPTY);
    return response.getStatus() >= 400 ?
        postNewEntity(uriInfo, accept, contentType, prefer, entitySetName, changes) :
        patchEntityInternal(uriInfo, accept, contentType, prefer, ifMatch, entitySetName, entityId, changes);
  }

  private Response replaceEntity(final UriInfo uriInfo,
      final String accept, final String prefer,
      final String entitySetName, final String entityId, final String entity) {

    try {
      final Accept acceptType = Accept.parse(accept);

      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final InputStream res = getUtilities(acceptType).addOrReplaceEntity(entityId, entitySetName,
          IOUtils.toInputStream(entity, Constants.ENCODING),
          xml.readEntity(acceptType, IOUtils.toInputStream(entity, Constants.ENCODING)));

      final ResWrap<Entity> cres;
      if (acceptType == Accept.ATOM) {
        cres = atomDeserializer.toEntity(res);
      } else {
        cres = jsonDeserializer.toEntity(res);
      }

      final String path = Commons.getEntityBasePath(entitySetName, entityId);
      FSManager.instance().putInMemory(
          cres, path + File.separatorChar + Constants.get(ConstantKey.ENTITY));

      final Response response;
      if ("return-content".equalsIgnoreCase(prefer)) {
        response = xml.createResponse(
            uriInfo.getRequestUri().toASCIIString(),
            xml.readEntity(entitySetName, entityId, acceptType).getValue(),
            null,
            acceptType,
            Response.Status.OK);
      } else {
        res.close();
        response = xml.createResponse(
            uriInfo.getRequestUri().toASCIIString(),
            null,
            null,
            acceptType,
            Response.Status.NO_CONTENT);
      }

      if (StringUtils.isNotBlank(prefer)) {
        response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer));
      }

      return response;
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @PUT
  @Path("/{entitySetName}({entityId})")
  @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON })
  @Consumes({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON })
  public Response replaceEntity(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId,
      final String entity) {

    try {
      getEntityInternal(uriInfo.getRequestUri().toASCIIString(),
          accept, entitySetName, entityId, accept, StringUtils.EMPTY, StringUtils.EMPTY);
      return replaceEntity(uriInfo, accept, prefer, entitySetName, entityId, entity);
    } catch (NotFoundException e) {
      return postNewEntity(uriInfo, accept, contentType, prefer, entitySetName, entityId);
    }
  }

  private StringBuilder containedPath(final String entityId, final String containedEntitySetName) {
    return new StringBuilder("Accounts").append(File.separatorChar).
        append(entityId).append(File.separatorChar).
        append("links").append(File.separatorChar).
        append(containedEntitySetName);
  }

  @DELETE
  @Path("/{entitySetName}({entityId})")
  public Response removeEntity(
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId) {

    try {
      final String basePath = entitySetName + File.separatorChar + Commons.getEntityKey(entityId);

      FSManager.instance().deleteEntity(basePath);

      return xml.createResponse(null, null, null, null, Response.Status.NO_CONTENT);
    } catch (Exception e) {
      return xml.createFaultResponse(Accept.XML.toString(), e);
    }
  }

  private Response replaceProperty(
      final String location,
      final String accept,
      final String contentType,
      final String prefer,
      final String entitySetName,
      final String entityId,
      final String path,
      final String format,
      final String changes,
      final boolean justValue) {

    // if the given path is not about any link then search for property
    LOG.info("Retrieve property {}", path);

    try {
      final FSManager fsManager = FSManager.instance();
      final String basePath = Commons.getEntityBasePath(entitySetName, entityId);

      final ResWrap<Entity> container = xml.readContainerEntity(Accept.ATOM,
          fsManager.readFile(basePath + Constants.get(ConstantKey.ENTITY), Accept.ATOM));

      final Entity entry = container.getPayload();

      Property toBeReplaced = null;
      for (String element : path.split("/")) {
        if (toBeReplaced == null) {
          toBeReplaced = entry.getProperty(element.trim());
        } else {
          List<Property> value = toBeReplaced.asComplex().getValue();
          for (Property field : value) {
            if (field.getName().equalsIgnoreCase(element)) {
              toBeReplaced = field;
            }
          }
        }
      }

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

      if (justValue) {
        // just for primitive values
        toBeReplaced.setValue(ValueType.PRIMITIVE, changes);
      } else {
        final Property pchanges = xml.readProperty(
            Accept.parse(contentType),
            IOUtils.toInputStream(changes, Constants.ENCODING));

        toBeReplaced.setValue(pchanges.getValueType(), pchanges.getValue());
      }

      fsManager.putInMemory(xml.writeEntity(Accept.ATOM, container),
          fsManager.getAbsolutePath(basePath + Constants.get(ConstantKey.ENTITY), Accept.ATOM));

      final Response response;
      if ("return-content".equalsIgnoreCase(prefer)) {
        response = getEntityInternal(location, accept, entitySetName, entityId, format, null, null);
      } else {
        Accept acceptType = null;
        if (StringUtils.isNotBlank(format)) {
          acceptType = Accept.valueOf(format.toUpperCase());
        } else if (StringUtils.isNotBlank(accept)) {
          acceptType = Accept.parse(accept, null);
        }

        response = xml.createResponse(null, null, null, acceptType, Response.Status.NO_CONTENT);
      }

      if (StringUtils.isNotBlank(prefer)) {
        response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer));
      }

      return response;
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  private Response deletePropertyValue(
      final String accept,
      final String prefer,
      final String entitySetName,
      final String entityId,
      final String path,
      final String format) {
    try {
      Accept acceptType = null;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else if (StringUtils.isNotBlank(accept)) {
        acceptType = Accept.parse(accept, null);
      }

      // if the given path is not about any link then search for property
      LOG.info("Retrieve property {}", path);

      final AbstractUtilities utils = getUtilities(acceptType);

      final InputStream changed = utils.deleteProperty(
          entitySetName,
          entityId,
          Arrays.asList(path.split("/")),
          acceptType);

      final Response response;
      if ("return-content".equalsIgnoreCase(prefer)) {
        response = xml.createResponse(null, changed, null, acceptType, Response.Status.OK);
      } else {
        changed.close();
        response = xml.createResponse(null, null, null, acceptType, Response.Status.NO_CONTENT);
      }

      if (StringUtils.isNotBlank(prefer)) {
        response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer));
      }

      return response;

    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  /**
   * Replace property value.
   *
   * @param accept
   * @param entitySetName
   * @param entityId
   * @param path
   * @param format
   * @param changes
   * @return response
   */
  @PUT
  @Path("/{entitySetName}({entityId})/{path:.*}/$value")
  public Response replacePropertyValue(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId,
      @PathParam("path") final String path,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String changes) {

    return replaceProperty(uriInfo.getRequestUri().toASCIIString(),
        accept, contentType, prefer, entitySetName, entityId, path, format, changes, true);
  }

  /**
   * Replace property.
   *
   * @param accept
   * @param entitySetName
   * @param entityId
   * @param path
   * @param format
   * @param changes
   * @return response
   */
  @PATCH
  @Path("/{entitySetName}({entityId})/{path:.*}")
  public Response patchProperty(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId,
      @PathParam("path") final String path,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String changes) {

    return replaceProperty(uriInfo.getRequestUri().toASCIIString(),
        accept, contentType, prefer, entitySetName, entityId, path, format, changes, false);
  }

  @PUT
  @Produces({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON })
  @Consumes({ MediaType.WILDCARD, MediaType.APPLICATION_OCTET_STREAM })
  @Path("/{entitySetName}({entityId})/$value")
  public Response replaceMediaEntity(
      @Context final UriInfo uriInfo,
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId,
      final String value) {
    try {

      final AbstractUtilities utils = getUtilities(null);

      final InputStream res = utils.putMediaInMemory(
          entitySetName, entityId, IOUtils.toInputStream(value, Constants.ENCODING));

      final String location = uriInfo.getRequestUri().toASCIIString().replace("/$value", "");

      final Response response;
      if ("return-content".equalsIgnoreCase(prefer)) {
        response = xml.createResponse(location, res, null, null, Response.Status.OK);
      } else {
        res.close();
        response = xml.createResponse(location, null, null, null, Response.Status.NO_CONTENT);
      }

      if (StringUtils.isNotBlank(prefer)) {
        response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer));
      }

      return response;

    } catch (Exception e) {
      LOG.error("Error retrieving entity", e);
      return xml.createFaultResponse(Accept.JSON.toString(), e);
    }
  }

  /**
   * Replace property.
   *
   * @param accept
   * @param entitySetName
   * @param entityId
   * @param path
   * @param format
   * @param changes
   * @return response
   */
  @PUT
  @Path("/{entitySetName}({entityId})/{path:.*}")
  public Response replaceProperty(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId,
      @PathParam("path") final String path,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String changes) {

    if (xml.isMediaContent(entitySetName + "/" + path)) {
      return replaceMediaProperty(prefer, entitySetName, entityId, path, changes);
    } else {
      return replaceProperty(uriInfo.getRequestUri().toASCIIString(),
          accept, contentType, prefer, entitySetName, entityId, path, format, changes, false);
    }
  }

  private Response replaceMediaProperty(
      final String prefer,
      final String entitySetName,
      final String entityId,
      final String path,
      final String value) {

    try {
      final AbstractUtilities utils = getUtilities(null);

      InputStream res = utils.putMediaInMemory(
          entitySetName, entityId, path, IOUtils.toInputStream(value, Constants.ENCODING));

      final Response response;
      if ("return-content".equalsIgnoreCase(prefer)) {
        response = xml.createResponse(null, res, null, null, Response.Status.OK);
      } else {
        res.close();
        response = xml.createResponse(null, null, null, null, Response.Status.NO_CONTENT);
      }

      if (StringUtils.isNotBlank(prefer)) {
        response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer));
      }

      return response;
    } catch (Exception e) {
      LOG.error("Error retrieving entity", e);
      return xml.createFaultResponse(Accept.JSON.toString(), e);
    }
  }

  /**
   * Nullify property value.
   *
   * @param accept
   * @param entitySetName
   * @param entityId
   * @param path
   * @param format
   * @return response
   */
  @DELETE
  @Path("/{entitySetName}({entityId})/{path:.*}/$value")
  public Response deleteProperty(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId,
      @PathParam("path") final String path,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {
    return deletePropertyValue(accept, prefer, entitySetName, entityId, path, format);
  }

  /**
   * Retrieve property sample.
   *
   * @param accept Accept header.
   * @param entitySetName Entity set name.
   * @param entityId entity id.
   * @param path path.
   * @param format format query option.
   * @return property.
   */
  @GET
  @Path("/{entitySetName}({entityId})/{path:.*}/$value")
  public Response getPathValue(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId,
      @PathParam("path") final String path,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    try {
      Accept acceptType = null;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else if (StringUtils.isNotBlank(accept)) {
        acceptType = Accept.parse(accept, null);
      }

      return navigateProperty(acceptType, entitySetName, entityId, path, true);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  /**
   * Retrieve property sample.
   *
   * @param accept Accept header.
   * @param entitySetName Entity set name.
   * @param entityId entity id.
   * @param path path.
   * @param format format query option.
   * @return property.
   */
  @GET
  @Path("/{entitySetName}({entityId})/{path:.*}")
  public Response getPath(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId,
      @PathParam("path") final String path,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    // default utilities
    final AbstractUtilities utils = xml;

    try {
      if (utils.isMediaContent(entitySetName + "/" + path)) {
        return navigateStreamedEntity(entitySetName, entityId, path);
      } else {
        Accept acceptType = null;
        if (StringUtils.isNotBlank(format)) {
          acceptType = Accept.valueOf(format.toUpperCase());
        } else if (StringUtils.isNotBlank(accept)) {
          acceptType = Accept.parse(accept, null);
        }

        try {
          final LinkInfo linkInfo = xml.readLinks(entitySetName, entityId, path, Accept.XML);
          final Map.Entry<String, List<String>> links = xml.extractLinkURIs(linkInfo.getLinks());
          final InputStream stream = xml.readEntities(links.getValue(), path, links.getKey(), linkInfo.isFeed());

          final ByteArrayOutputStream content = new ByteArrayOutputStream();
          final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);

          final ResWrap<?> container = linkInfo.isFeed() ? atomDeserializer.toEntitySet(stream) : atomDeserializer.
              toEntity(stream);
          if (acceptType == Accept.ATOM) {
            atomSerializer.write(writer, container);
          } else {
            jsonSerializer.write(writer, container);
          }
          writer.flush();
          writer.close();

          final String basePath = Commons.getEntityBasePath(entitySetName, entityId);

          return xml.createResponse(
              null,
              new ByteArrayInputStream(content.toByteArray()),
              Commons.getETag(basePath),
              acceptType);

        } catch (NotFoundException e) {
          // if the given path is not about any link then search for property
          return navigateProperty(acceptType, entitySetName, entityId, path, false);
        }
      }
    } catch (Exception e) {
      return utils.createFaultResponse(accept, e);
    }
  }

  private Response navigateStreamedEntity(
      final String entitySetName,
      final String entityId,
      final String path) throws Exception {

    final AbstractUtilities utils = getUtilities(null);
    final Map.Entry<String, InputStream> entityInfo = utils.readMediaEntity(entitySetName, entityId, path);
    return utils.createResponse(null, entityInfo.getValue(), Commons.getETag(entityInfo.getKey()), null);
  }

  private Response navigateProperty(
      final Accept acceptType,
      final String entitySetName,
      final String entityId,
      final String path,
      final boolean searchForValue) throws Exception {

    if ((searchForValue && acceptType != null && acceptType != Accept.TEXT) || acceptType == Accept.ATOM) {
      throw new UnsupportedMediaTypeException("Unsupported media type " + acceptType);
    }

    final AbstractUtilities utils = getUtilities(acceptType);

    final Map.Entry<String, InputStream> entityInfo = utils.readEntity(entitySetName, entityId, Accept.ATOM);

    final InputStream entity = entityInfo.getValue();

    final ResWrap<Entity> entryContainer = atomDeserializer.toEntity(entity);

    final String[] pathElems = StringUtils.split(path, "/");
    Property property = entryContainer.getPayload().getProperty(pathElems[0]);
    if (pathElems.length > 1 && property.isComplex()) {
      for (Property sub : property.asComplex().getValue()) {
        if (pathElems[1].equals(sub.getName())) {
          property = sub;
          if (pathElems.length > 2 && property.isComplex()) {
            for (Property subsub : property.asComplex().getValue()) {
              if (pathElems[2].equals(subsub.getName())) {
                property = subsub;
              }
            }
          }
        }
      }
    }

    final ResWrap<Property> container = new ResWrap<Property>(
        URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + entitySetName + "(" + entityId + ")/" + path),
        entryContainer.getMetadataETag(),
        property);

    return xml.createResponse(null,
        searchForValue ? IOUtils.toInputStream(
            container.getPayload().isNull() ? StringUtils.EMPTY : stringValue(container.getPayload()),
                Constants.ENCODING) : utils.writeProperty(acceptType, container),
                Commons.getETag(Commons.getEntityBasePath(entitySetName, entityId)),
                acceptType);
  }

  private String stringValue(final Property property) {
    EdmPrimitiveTypeKind kind = EdmPrimitiveTypeKind.valueOfFQN(property.getType());
    try {
      return EdmPrimitiveTypeFactory.getInstance(kind)
          .valueToString(property.asPrimitive(), null, null,
              org.apache.olingo.commons.api.Constants.DEFAULT_PRECISION,
              org.apache.olingo.commons.api.Constants.DEFAULT_SCALE, null);
    } catch (final EdmPrimitiveTypeException e) {
      return property.asPrimitive().toString();
    }
  }

  /**
   * Count sample.
   *
   * @param accept Accept header.
   * @param entitySetName entity set name.
   * @return count.
   */
  @GET
  @Path("/{entitySetName}/$count")
  public Response count(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entitySetName") final String entitySetName) {
    try {
      final Accept acceptType = Accept.parse(accept, Accept.TEXT);

      if (acceptType != Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported type " + accept);
      }

      int count = xml.countAllElements(entitySetName);

      final Response.ResponseBuilder builder = Response.ok();
      builder.entity(count);

      return builder.build();
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @GET
  @Path("/Accounts({entityId})/{containedEntitySetName}({containedEntityId})")
  public Response getContainedEntity(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entityId") final String entityId,
      @PathParam("containedEntitySetName") final String containedEntitySetName,
      @PathParam("containedEntityId") final String containedEntityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }
      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final StringBuilder containedPath = containedPath(entityId, containedEntitySetName);
      if (StringUtils.isNotBlank(containedEntityId)) {
        containedPath.append('(').append(containedEntityId).append(')');
      }
      final InputStream entry = FSManager.instance().readFile(containedPath.toString(), Accept.ATOM);

      final ResWrap<Entity> container = atomDeserializer.toEntity(entry);

      return xml.createResponse(
          null,
          xml.writeEntity(acceptType, container),
          null,
          acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/Accounts({entityId})/{containedEntitySetName:.*}")
  public Response postContainedEntity(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @PathParam("entityId") final String entityId,
      @PathParam("containedEntitySetName") final String containedEntitySetName,
      final String entity) {

    try {
      final Accept acceptType = Accept.parse(accept);
      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final AbstractUtilities utils = getUtilities(acceptType);

      // 1. parse the entry (from Atom or JSON)
      final ResWrap<Entity> entryContainer;
      final Entity entry;
      final Accept contentTypeValue = Accept.parse(contentType);
      if (Accept.ATOM == contentTypeValue) {
        entryContainer = atomDeserializer.toEntity(IOUtils.toInputStream(entity, Constants.ENCODING));
        entry = entryContainer.getPayload();
      } else {
        final ResWrap<Entity> jcontainer = jsonDeserializer.toEntity(
            IOUtils.toInputStream(entity, Constants.ENCODING));
        entry = jcontainer.getPayload();

        entryContainer = new ResWrap<Entity>(
            jcontainer.getContextURL(),
            jcontainer.getMetadataETag(),
            entry);
      }

      final EdmTypeInfo contained = new EdmTypeInfo.Builder().setTypeExpression(metadata.
          getNavigationProperties("Accounts").get(containedEntitySetName).getType()).build();
      final String entityKey = getUtilities(contentTypeValue).
          getDefaultEntryKey(contained.getFullQualifiedName().getName(), entry);

      // 2. Store the new entity
      final String atomEntryRelativePath = containedPath(entityId, containedEntitySetName).
          append('(').append(entityKey).append(')').toString();
      FSManager.instance().putInMemory(
          utils.writeEntity(Accept.ATOM, entryContainer),
          FSManager.instance().getAbsolutePath(atomEntryRelativePath, Accept.ATOM));

      // 3. Update the contained entity set
      final String atomFeedRelativePath = containedPath(entityId, containedEntitySetName).toString();
      final InputStream feedIS = FSManager.instance().readFile(atomFeedRelativePath, Accept.ATOM);
      final ResWrap<EntityCollection> feedContainer = atomDeserializer.toEntitySet(feedIS);
      feedContainer.getPayload().getEntities().add(entry);

      final ByteArrayOutputStream content = new ByteArrayOutputStream();
      final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);
      atomSerializer.write(writer, feedContainer);
      writer.flush();
      writer.close();

      FSManager.instance().putInMemory(
          new ByteArrayInputStream(content.toByteArray()),
          FSManager.instance().getAbsolutePath(atomFeedRelativePath, Accept.ATOM));

      // Finally, return
      return utils.createResponse(
          uriInfo.getRequestUri().toASCIIString() + "(" + entityKey + ")",
          utils.writeEntity(acceptType, entryContainer),
          null,
          acceptType,
          Response.Status.CREATED);
    } catch (Exception e) {
      LOG.error("While creating new contained entity", e);
      return xml.createFaultResponse(accept, e);
    }
  }

  @PATCH
  @Path("/{entitySetName}({entityId})/{containedEntitySetName}({containedEntityId})")
  public Response patchContainedEntity(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @PathParam("entitySetName") final String entitySetName,
      @PathParam("entityId") final String entityId,
      @PathParam("containedEntitySetName") final String containedEntitySetName,
      @PathParam("containedEntityId") final String containedEntityId,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String changes) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }
      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      final Accept contentTypeValue;
      if (StringUtils.isBlank(contentType)) {
        throw new IllegalArgumentException();
      }
      contentTypeValue = Accept.parse(contentType);

      final LinkInfo links = xml.readLinks(
          entitySetName, entityId, containedEntitySetName + "(" + containedEntityId + ")", Accept.ATOM);

      ResWrap<Entity> container = atomDeserializer.toEntity(links.getLinks());
      final Entity original = container.getPayload();

      final Entity entryChanges;
      if (Accept.ATOM == contentTypeValue) {
        container = atomDeserializer.toEntity(IOUtils.toInputStream(changes, Constants.ENCODING));
        entryChanges = container.getPayload();
      } else {
        final String entityType = metadata.getEntitySet(entitySetName).getType();
        final String containedType = metadata.getEntityOrComplexType(entityType).
            getNavigationProperty(containedEntitySetName).getType();
        final EdmTypeInfo typeInfo = new EdmTypeInfo.Builder().setTypeExpression(containedType).build();

        final ResWrap<Entity> jsonContainer = jsonDeserializer.toEntity(
            IOUtils.toInputStream(changes, Constants.ENCODING));
        jsonContainer.getPayload().setType(typeInfo.getFullQualifiedName().toString());
        entryChanges = jsonContainer.getPayload();
      }

      for (Property property : entryChanges.getProperties()) {
        final Property old = original.getProperty(property.getName());
        if (old != null) {
          original.getProperties().remove(old);
        }
        original.getProperties().add(property);
      }

      FSManager.instance().putInMemory(new ResWrap<Entity>((URI) null, null, original),
          xml.getLinksBasePath(entitySetName, entityId) + containedEntitySetName + "(" + containedEntityId + ")");

      return xml.createResponse(null, null, acceptType, Response.Status.NO_CONTENT);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @DELETE
  @Path("/Accounts({entityId})/{containedEntitySetName}({containedEntityId})")
  public Response removeContainedEntity(
      @PathParam("entityId") final String entityId,
      @PathParam("containedEntitySetName") final String containedEntitySetName,
      @PathParam("containedEntityId") final String containedEntityId) {

    try {
      // 1. Fetch the contained entity to be removed
      final InputStream entry = FSManager.instance().
          readFile(containedPath(entityId, containedEntitySetName).
              append('(').append(containedEntityId).append(')').toString(), Accept.ATOM);
      final ResWrap<Entity> container = atomDeserializer.toEntity(entry);

      // 2. Remove the contained entity
      final String atomEntryRelativePath = containedPath(entityId, containedEntitySetName).
          append('(').append(containedEntityId).append(')').toString();
      FSManager.instance().deleteFile(atomEntryRelativePath);

      // 3. Update the contained entity set
      final String atomFeedRelativePath = containedPath(entityId, containedEntitySetName).toString();
      final InputStream feedIS = FSManager.instance().readFile(atomFeedRelativePath, Accept.ATOM);
      final ResWrap<EntityCollection> feedContainer = atomDeserializer.toEntitySet(feedIS);
      feedContainer.getPayload().getEntities().remove(container.getPayload());

      final ByteArrayOutputStream content = new ByteArrayOutputStream();
      final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);
      atomSerializer.write(writer, feedContainer);
      writer.flush();
      writer.close();

      FSManager.instance().putInMemory(
          new ByteArrayInputStream(content.toByteArray()),
          FSManager.instance().getAbsolutePath(atomFeedRelativePath, Accept.ATOM));

      return xml.createResponse(null, null, null, null, Response.Status.NO_CONTENT);
    } catch (Exception e) {
      return xml.createFaultResponse(Accept.XML.toString(), e);
    }
  }

  @GET
  @Path("/Accounts({entityId})/{containedEntitySetName:.*}")
  public Response getContainedEntitySet(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @PathParam("entityId") final String entityId,
      @PathParam("containedEntitySetName") final String containedEntitySetName,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    String tempContainedESName = containedEntitySetName;
    if ("MyGiftCard".equals(tempContainedESName)) {
      return getContainedEntity(accept, entityId, tempContainedESName, null, format);
    }

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }
      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
        throw new UnsupportedMediaTypeException("Unsupported media type");
      }

      String derivedType = null;
      if (tempContainedESName.contains("/")) {
        final String[] parts = tempContainedESName.split("/");
        tempContainedESName = parts[0];
        derivedType = parts[1];
      }

      final InputStream feed = FSManager.instance().
          readFile(containedPath(entityId, tempContainedESName).toString(), Accept.ATOM);

      final ResWrap<EntityCollection> container = atomDeserializer.toEntitySet(feed);

      if (derivedType != null) {
        final List<Entity> nonMatching = new ArrayList<Entity>();
        for (Entity entity : container.getPayload().getEntities()) {
          if (!derivedType.equals(entity.getType())) {
            nonMatching.add(entity);
          }
        }
        container.getPayload().getEntities().removeAll(nonMatching);
      }

      return xml.createResponse(
          null,
          xml.writeEntitySet(acceptType, container),
          null,
          acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @GET
  @Path("/GetDefaultColor()")
  public Response functionGetDefaultColor(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final Property property = new Property();
      property.setType("Microsoft.Test.OData.Services.ODataWCFService.Color");
      property.setValue(ValueType.ENUM, "Red");
      final ResWrap<Property> container = new ResWrap<Property>(
          URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + property.getType()), null,
          property);

      return xml.createResponse(
          null,
          xml.writeProperty(acceptType, container),
          null,
          acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @GET
  @Path("/GetPerson2({param:.*})")
  public Response functionGetPerson2(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    return getEntityInternal(
        uriInfo.getRequestUri().toASCIIString(), accept, "Customers", "1", format, null, null);
  }

  @GET
  @Path("/GetPerson2({param:.*})/Emails")
  public Response functionGetPerson2Emails(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    return getPath(accept, "Customers", "1", "Emails", format);
  }

  @GET
  @Path("/GetPerson2({param:.*})/HomeAddress")
  public Response functionGetPerson2HomeAddress(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    return getPath(accept, "Customers", "1", "HomeAddress", format);
  }

  @GET
  @Path("/GetPerson2({param:.*})/Parent")
  public Response functionGetPerson2Parent(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    return getEntityInternal(
        uriInfo.getRequestUri().toASCIIString(), accept, "Customers", "2", format, null, null);
  }

  @GET
  @Path("/GetPerson({param:.*})")
  public Response functionGetPerson(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    return getEntityInternal(
        uriInfo.getRequestUri().toASCIIString(), accept, "Customers", "1", format, null, null);
  }

  @GET
  @Path("/GetAllProducts()")
  public Response functionGetAllProducts(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    return getEntitySet(uriInfo, accept, "Products", null, null, format, null, null, null, null);
  }

  @GET
  @Path("/GetProductsByAccessLevel({param:.*})")
  public Response functionGetProductsByAccessLevel(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final Property property = new Property();
      property.setType("Collection(String)");
      final List<String> value = Arrays.asList("Cheetos", "Mushrooms", "Apple", "Car", "Computer");
      property.setValue(ValueType.COLLECTION_PRIMITIVE, value);
      final ResWrap<Property> container = new ResWrap<Property>(
          URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + property.getType()), null,
          property);

      return xml.createResponse(
          null,
          xml.writeProperty(acceptType, container),
          null,
          acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @GET
  @Path("/GetBossEmails({param:.*})")
  public Response functionGetBossEmails(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final Property property = new Property();
      property.setType("Collection(Edm.String)");
      property.setValue(ValueType.COLLECTION_PRIMITIVE,
          Arrays.asList("first@olingo.apache.org", "second@olingo.apache.org"));
      final ResWrap<Property> container = new ResWrap<Property>(
          URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + property.getType()), null,
          property);

      return xml.createResponse(null, xml.writeProperty(acceptType, container), null, acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/Discount()")
  public Response actionDiscount(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String param) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final Accept contentTypeValue = Accept.parse(contentType);
      Property property;
      if (contentTypeValue == Accept.ATOM) {
        final ResWrap<Property> paramContainer = atomDeserializer.toProperty(
            IOUtils.toInputStream(param, Constants.ENCODING));
        property = paramContainer.getPayload();
      } else {
        final ResWrap<Property> paramContainer = jsonDeserializer.toProperty(
            IOUtils.toInputStream(param, Constants.ENCODING));
        property = paramContainer.getPayload();
      }

      assert property.isComplex();
      assert 1 == property.asComplex().getValue().size();
      assert "Edm.Int32".equals(property.asComplex().getValue().get(0).getType());
      assert property.asComplex().getValue().get(0).isPrimitive();
      assert "percentage".equals(property.asComplex().getValue().get(0).getName());

      return xml.createResponse(null, null, null, acceptType, Response.Status.NO_CONTENT);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/GetAllProducts()/Discount")
  public Response actionBoundDiscount() {
    try {
      final String basePath = "Products" + File.separatorChar + "feed";

      final InputStream feed = FSManager.instance().readFile(basePath, Accept.JSON_FULLMETA);
      return xml.createResponse(null, feed, Commons.getETag(basePath), Accept.JSON_FULLMETA);
    } catch (Exception e) {
      return xml.createFaultResponse(Accept.JSON_FULLMETA.toString(), e);
    }
  }

  @POST
  @Path("/ResetBossAddress()")
  public Response actionResetBossAddress(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String param) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final Accept contentTypeValue = Accept.parse(contentType);
      final Entity entity = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING));

      assert "Microsoft.Test.OData.Services.ODataWCFService.Address".equals(entity.getType());
      assert entity.getProperty("address").isComplex();

      final ResWrap<Property> result = new ResWrap<Property>(
          URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX)
              + "Microsoft.Test.OData.Services.ODataWCFService.Address"),
          null,
          entity.getProperty("address"));

      return xml.createResponse(
          null,
          xml.writeProperty(acceptType, result),
          null,
          acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/ResetBossEmail()")
  public Response actionResetBossEmail(
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      final String param) {

    try {
      final Accept acceptType;
      if (StringUtils.isNotBlank(format)) {
        acceptType = Accept.valueOf(format.toUpperCase());
      } else {
        acceptType = Accept.parse(accept);
      }

      final Accept contentTypeValue = Accept.parse(contentType);
      final Entity entry = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING));

      assert 1 == entry.getProperties().size();
      assert "Collection(Edm.String)".equals(entry.getProperty("emails").getType());
      assert entry.getProperty("emails").isCollection();

      return xml.createResponse(
          null,
          xml.writeProperty(acceptType, entry.getProperty("emails")),
          null,
          acceptType);
    } catch (Exception e) {
      return xml.createFaultResponse(accept, e);
    }
  }

  @POST
  @Path("/Products({productId})/Categories/$ref")
  public Response createLinked() {
    return xml.createResponse(null, null, null, Status.NO_CONTENT);
  }

  @POST
  @Path("/Customers(1)/Orders/$ref")
  public Response linkOrderViaRef() {
    return xml.createResponse(null, null, null, Status.NO_CONTENT);
  }

  @DELETE
  @Path("/Products({productId})/Categories({categoryId})/$ref")
  public Response deleteLinked() {
    return xml.createResponse(null, null, null, Status.NO_CONTENT);
  }

  @GET
  @Path("/Company/VipCustomer")
  public Response getVipCustomer(
      @Context final UriInfo uriInfo,
      @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
      @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
      @QueryParam("$expand") @DefaultValue(StringUtils.EMPTY) final String expand,
      @QueryParam("$select") @DefaultValue(StringUtils.EMPTY) final String select) {

    return getEntityInternal(
        uriInfo.getRequestUri().toASCIIString(), accept, "VipCustomer", "1", format, expand, select);
  }

  protected Map.Entry<Accept, AbstractUtilities> getUtilities(final String accept, final String format) {
    Accept acceptType;
    if (StringUtils.isNotBlank(format)) {
      try {
        acceptType = Accept.valueOf(format.toUpperCase());
      } catch (Exception e) {
        acceptType = Accept.parse(format);
      }
    } else {
      acceptType = Accept.parse(accept);
    }

    return new AbstractMap.SimpleEntry<Accept, AbstractUtilities>(acceptType, getUtilities(acceptType));
  }

  protected AbstractUtilities getUtilities(final Accept accept) {
    final AbstractUtilities utils;
    if (accept == Accept.XML || accept == Accept.TEXT || accept == Accept.ATOM) {
      utils = xml;
    } else {
      utils = json;
    }

    return utils;
  }
}
