@Override
  public InputStream execute(CloseableHttpClient httpclient, String uri, String queryParam)
      throws WxErrorException, IOException {
    if (queryParam != null) {
      if (uri.indexOf('?') == -1) {
        uri += '?';
      }
      uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
    }

    HttpGet httpGet = new HttpGet(uri);

    try (CloseableHttpResponse response = httpclient.execute(httpGet)) {

      Header[] contentTypeHeader = response.getHeaders("Content-Type");
      if (contentTypeHeader != null && contentTypeHeader.length > 0) {
        // 下载媒体文件出错
        if (ContentType.TEXT_PLAIN.getMimeType().equals(contentTypeHeader[0].getValue())) {
          String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
          throw new WxErrorException(JSON.parseObject(responseContent, WxError.class));
        }
      }
      InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response);

      // 视频文件不支持下载
      String fileName = getFileName(response);
      if (StringUtils.isBlank(fileName)) {
        return null;
      }

      return inputStream;
    }
  }
 public void testScrollEntity() throws IOException {
   String scroll = randomAsciiOfLength(30);
   HttpEntity entity = scrollEntity(scroll);
   assertEquals(ContentType.TEXT_PLAIN.toString(), entity.getContentType().getValue());
   assertEquals(
       scroll,
       Streams.copyToString(new InputStreamReader(entity.getContent(), StandardCharsets.UTF_8)));
 }
Example #3
0
 private ContentType receiveWithCharsetParameter(ContentType contentType, Charset charset) {
   if (contentType.getCharset() != null) {
     return contentType;
   }
   final String mimeType = contentType.getMimeType();
   if (mimeType.equals(ContentType.TEXT_PLAIN.getMimeType())
       || AbstractFutureCallback.ODATA_MIME_TYPE.matcher(mimeType).matches()) {
     return contentType.withCharset(charset);
   }
   return contentType;
 }
Example #4
0
/** Application API used by Olingo2 Component. */
public final class Olingo2AppImpl implements Olingo2App {

  public static final String METADATA = "$metadata";

  private static final String SEPARATOR = "/";

  private static final String BOUNDARY_PREFIX = "batch_";
  private static final String BOUNDARY_PARAMETER = "; boundary=";

  private static final ContentType METADATA_CONTENT_TYPE =
      ContentType.create("application/xml", Consts.UTF_8);
  private static final ContentType SERVICE_DOCUMENT_CONTENT_TYPE =
      ContentType.create("application/atomsvc+xml", Consts.UTF_8);
  private static final String BATCH_CONTENT_TYPE = ContentType.create("multipart/mixed").toString();

  private static final String BATCH = "$batch";
  private static final String MAX_DATA_SERVICE_VERSION =
      "Max" + ODataHttpHeaders.DATASERVICEVERSION;
  private static final String MULTIPART_MIME_TYPE = "multipart/";
  private static final ContentType TEXT_PLAIN_WITH_CS_UTF_8 =
      ContentType.TEXT_PLAIN.withCharset(Consts.UTF_8);

  private final CloseableHttpAsyncClient client;

  private String serviceUri;
  private ContentType contentType;
  private Map<String, String> httpHeaders;

  /** Create Olingo2 Application with default HTTP configuration. */
  public Olingo2AppImpl(String serviceUri) {
    this(serviceUri, null);
  }

  /**
   * Create Olingo2 Application with custom HTTP client builder.
   *
   * @param serviceUri Service Application base URI.
   * @param builder custom HTTP client builder.
   */
  public Olingo2AppImpl(String serviceUri, HttpAsyncClientBuilder builder) {
    setServiceUri(serviceUri);

    if (builder == null) {
      this.client = HttpAsyncClients.createDefault();
    } else {
      this.client = builder.build();
    }
    this.client.start();
    this.contentType = ContentType.create("application/json", Consts.UTF_8);
  }

  @Override
  public void setServiceUri(String serviceUri) {
    if (serviceUri == null || serviceUri.isEmpty()) {
      throw new IllegalArgumentException("serviceUri");
    }
    this.serviceUri =
        serviceUri.endsWith(SEPARATOR)
            ? serviceUri.substring(0, serviceUri.length() - 1)
            : serviceUri;
  }

  @Override
  public String getServiceUri() {
    return serviceUri;
  }

  @Override
  public Map<String, String> getHttpHeaders() {
    return httpHeaders;
  }

  @Override
  public void setHttpHeaders(Map<String, String> httpHeaders) {
    this.httpHeaders = httpHeaders;
  }

  @Override
  public String getContentType() {
    return contentType.toString();
  }

  @Override
  public void setContentType(String contentType) {
    this.contentType = ContentType.parse(contentType);
  }

  @Override
  public void close() {
    HttpAsyncClientUtils.closeQuietly(client);
  }

  @Override
  public <T> void read(
      final Edm edm,
      final String resourcePath,
      final Map<String, String> queryParams,
      final Olingo2ResponseHandler<T> responseHandler) {

    final UriInfoWithType uriInfo = parseUri(edm, resourcePath, queryParams);

    execute(
        new HttpGet(createUri(resourcePath, queryParams)),
        getResourceContentType(uriInfo),
        new AbstractFutureCallback<T>(responseHandler) {

          @Override
          @SuppressWarnings("unchecked")
          public void onCompleted(HttpResponse result) throws IOException {

            readContent(
                uriInfo,
                result.getEntity() != null ? result.getEntity().getContent() : null,
                responseHandler);
          }
        });
  }

  private ContentType getResourceContentType(UriInfoWithType uriInfo) {
    ContentType resourceContentType;
    switch (uriInfo.getUriType()) {
      case URI0:
        // service document
        resourceContentType = SERVICE_DOCUMENT_CONTENT_TYPE;
        break;
      case URI8:
        // metadata
        resourceContentType = METADATA_CONTENT_TYPE;
        break;
      case URI4:
      case URI5:
        // is it a $value URI??
        if (uriInfo.isValue()) {
          // property value and $count
          resourceContentType = TEXT_PLAIN_WITH_CS_UTF_8;
        } else {
          resourceContentType = contentType;
        }
        break;
      case URI15:
      case URI16:
      case URI50A:
      case URI50B:
        // $count
        resourceContentType = TEXT_PLAIN_WITH_CS_UTF_8;
        break;
      default:
        resourceContentType = contentType;
    }
    return resourceContentType;
  }

  @Override
  public <T> void create(
      Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler) {
    final UriInfoWithType uriInfo = parseUri(edm, resourcePath, null);

    writeContent(edm, new HttpPost(createUri(resourcePath, null)), uriInfo, data, responseHandler);
  }

  @Override
  public <T> void update(
      Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler) {
    final UriInfoWithType uriInfo = parseUri(edm, resourcePath, null);

    writeContent(edm, new HttpPut(createUri(resourcePath, null)), uriInfo, data, responseHandler);
  }

  @Override
  public <T> void patch(
      Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler) {
    final UriInfoWithType uriInfo = parseUri(edm, resourcePath, null);

    writeContent(edm, new HttpPatch(createUri(resourcePath, null)), uriInfo, data, responseHandler);
  }

  @Override
  public <T> void merge(
      Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler) {
    final UriInfoWithType uriInfo = parseUri(edm, resourcePath, null);

    writeContent(edm, new HttpMerge(createUri(resourcePath, null)), uriInfo, data, responseHandler);
  }

  @Override
  public void batch(
      Edm edm, Object data, Olingo2ResponseHandler<List<Olingo2BatchResponse>> responseHandler) {
    final UriInfoWithType uriInfo = parseUri(edm, BATCH, null);

    writeContent(edm, new HttpPost(createUri(BATCH, null)), uriInfo, data, responseHandler);
  }

  @Override
  public void delete(
      String resourcePath, final Olingo2ResponseHandler<HttpStatusCodes> responseHandler) {

    execute(
        new HttpDelete(createUri(resourcePath)),
        contentType,
        new AbstractFutureCallback<HttpStatusCodes>(responseHandler) {
          @Override
          public void onCompleted(HttpResponse result) {
            final StatusLine statusLine = result.getStatusLine();
            responseHandler.onResponse(HttpStatusCodes.fromStatusCode(statusLine.getStatusCode()));
          }
        });
  }

  private <T> void readContent(
      UriInfoWithType uriInfo, InputStream content, Olingo2ResponseHandler<T> responseHandler) {
    try {
      responseHandler.onResponse(this.<T>readContent(uriInfo, content));
    } catch (EntityProviderException e) {
      responseHandler.onException(e);
    } catch (ODataApplicationException e) {
      responseHandler.onException(e);
    }
  }

  @SuppressWarnings("unchecked")
  private <T> T readContent(UriInfoWithType uriInfo, InputStream content)
      throws EntityProviderException, ODataApplicationException {
    T response;
    switch (uriInfo.getUriType()) {
      case URI0:
        // service document
        response =
            (T)
                EntityProvider.readServiceDocument(
                    content, SERVICE_DOCUMENT_CONTENT_TYPE.toString());
        break;

      case URI8:
        // $metadata
        response = (T) EntityProvider.readMetadata(content, false);
        break;

      case URI7A:
        // link
        response =
            (T) EntityProvider.readLink(getContentType(), uriInfo.getTargetEntitySet(), content);
        break;

      case URI7B:
        // links
        response =
            (T) EntityProvider.readLinks(getContentType(), uriInfo.getTargetEntitySet(), content);
        break;

      case URI3:
        // complex property
        final List<EdmProperty> complexPropertyPath = uriInfo.getPropertyPath();
        final EdmProperty complexProperty = complexPropertyPath.get(complexPropertyPath.size() - 1);
        response =
            (T)
                EntityProvider.readProperty(
                    getContentType(),
                    complexProperty,
                    content,
                    EntityProviderReadProperties.init().build());
        break;

      case URI4:
      case URI5:
        // simple property
        final List<EdmProperty> simplePropertyPath = uriInfo.getPropertyPath();
        final EdmProperty simpleProperty = simplePropertyPath.get(simplePropertyPath.size() - 1);
        if (uriInfo.isValue()) {
          response = (T) EntityProvider.readPropertyValue(simpleProperty, content);
        } else {
          response =
              (T)
                  EntityProvider.readProperty(
                      getContentType(),
                      simpleProperty,
                      content,
                      EntityProviderReadProperties.init().build());
        }
        break;

      case URI15:
      case URI16:
      case URI50A:
      case URI50B:
        // $count
        final String stringCount = new String(EntityProvider.readBinary(content), Consts.UTF_8);
        response = (T) Long.valueOf(stringCount);
        break;

      case URI1:
      case URI6B:
        if (uriInfo.getCustomQueryOptions().containsKey("!deltatoken")) {
          // ODataDeltaFeed
          response =
              (T)
                  EntityProvider.readDeltaFeed(
                      getContentType(),
                      uriInfo.getTargetEntitySet(),
                      content,
                      EntityProviderReadProperties.init().build());
        } else {
          // ODataFeed
          response =
              (T)
                  EntityProvider.readFeed(
                      getContentType(),
                      uriInfo.getTargetEntitySet(),
                      content,
                      EntityProviderReadProperties.init().build());
        }
        break;

      case URI2:
      case URI6A:
        response =
            (T)
                EntityProvider.readEntry(
                    getContentType(),
                    uriInfo.getTargetEntitySet(),
                    content,
                    EntityProviderReadProperties.init().build());
        break;

      default:
        throw new ODataApplicationException(
            "Unsupported resource type " + uriInfo.getTargetType(), Locale.ENGLISH);
    }

    return response;
  }

  private <T> void writeContent(
      final Edm edm,
      HttpEntityEnclosingRequestBase httpEntityRequest,
      final UriInfoWithType uriInfo,
      final Object content,
      final Olingo2ResponseHandler<T> responseHandler) {

    try {
      // process resource by UriType
      final ODataResponse response = writeContent(edm, uriInfo, content);

      // copy all response headers
      for (String header : response.getHeaderNames()) {
        httpEntityRequest.setHeader(header, response.getHeader(header));
      }

      // get (http) entity which is for default Olingo2 implementation an InputStream
      if (response.getEntity() instanceof InputStream) {
        httpEntityRequest.setEntity(new InputStreamEntity((InputStream) response.getEntity()));
        /*
                        // avoid sending it without a header field set
                        if (!httpEntityRequest.containsHeader(HttpHeaders.CONTENT_TYPE)) {
                            httpEntityRequest.addHeader(HttpHeaders.CONTENT_TYPE, getContentType());
                        }
        */
      }

      // execute HTTP request
      final Header requestContentTypeHeader =
          httpEntityRequest.getFirstHeader(HttpHeaders.CONTENT_TYPE);
      final ContentType requestContentType =
          requestContentTypeHeader != null
              ? ContentType.parse(requestContentTypeHeader.getValue())
              : contentType;
      execute(
          httpEntityRequest,
          requestContentType,
          new AbstractFutureCallback<T>(responseHandler) {
            @SuppressWarnings("unchecked")
            @Override
            public void onCompleted(HttpResponse result)
                throws IOException, EntityProviderException, BatchException,
                    ODataApplicationException {

              // if a entity is created (via POST request) the response body contains the new
              // created entity
              HttpStatusCodes statusCode =
                  HttpStatusCodes.fromStatusCode(result.getStatusLine().getStatusCode());

              // look for no content, or no response body!!!
              final boolean noEntity =
                  result.getEntity() == null || result.getEntity().getContentLength() == 0;
              if (statusCode == HttpStatusCodes.NO_CONTENT || noEntity) {
                responseHandler.onResponse(
                    (T) HttpStatusCodes.fromStatusCode(result.getStatusLine().getStatusCode()));
              } else {

                switch (uriInfo.getUriType()) {
                  case URI9:
                    // $batch
                    final List<BatchSingleResponse> singleResponses =
                        EntityProvider.parseBatchResponse(
                            result.getEntity().getContent(),
                            result.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());

                    // parse batch response bodies
                    final List<Olingo2BatchResponse> responses =
                        new ArrayList<Olingo2BatchResponse>();
                    Map<String, String> contentIdLocationMap = new HashMap<String, String>();

                    final List<Olingo2BatchRequest> batchRequests =
                        (List<Olingo2BatchRequest>) content;
                    final Iterator<Olingo2BatchRequest> iterator = batchRequests.iterator();

                    for (BatchSingleResponse response : singleResponses) {
                      final Olingo2BatchRequest request = iterator.next();

                      if (request instanceof Olingo2BatchChangeRequest
                          && ((Olingo2BatchChangeRequest) request).getContentId() != null) {

                        contentIdLocationMap.put(
                            "$" + ((Olingo2BatchChangeRequest) request).getContentId(),
                            response.getHeader(HttpHeaders.LOCATION));
                      }

                      try {
                        responses.add(parseResponse(edm, contentIdLocationMap, request, response));
                      } catch (Exception e) {
                        // report any parsing errors as error response
                        responses.add(
                            new Olingo2BatchResponse(
                                Integer.parseInt(response.getStatusCode()),
                                response.getStatusInfo(),
                                response.getContentId(),
                                response.getHeaders(),
                                new ODataApplicationException(
                                    "Error parsing response for " + request + ": " + e.getMessage(),
                                    Locale.ENGLISH,
                                    e)));
                      }
                    }
                    responseHandler.onResponse((T) responses);
                    break;

                  case URI4:
                  case URI5:
                    // simple property
                    // get the response content as Object for $value or Map<String, Object>
                    // otherwise
                    final List<EdmProperty> simplePropertyPath = uriInfo.getPropertyPath();
                    final EdmProperty simpleProperty =
                        simplePropertyPath.get(simplePropertyPath.size() - 1);
                    if (uriInfo.isValue()) {
                      responseHandler.onResponse(
                          (T)
                              EntityProvider.readPropertyValue(
                                  simpleProperty, result.getEntity().getContent()));
                    } else {
                      responseHandler.onResponse(
                          (T)
                              EntityProvider.readProperty(
                                  getContentType(),
                                  simpleProperty,
                                  result.getEntity().getContent(),
                                  EntityProviderReadProperties.init().build()));
                    }
                    break;

                  case URI3:
                    // complex property
                    // get the response content as Map<String, Object>
                    final List<EdmProperty> complexPropertyPath = uriInfo.getPropertyPath();
                    final EdmProperty complexProperty =
                        complexPropertyPath.get(complexPropertyPath.size() - 1);
                    responseHandler.onResponse(
                        (T)
                            EntityProvider.readProperty(
                                getContentType(),
                                complexProperty,
                                result.getEntity().getContent(),
                                EntityProviderReadProperties.init().build()));
                    break;

                  case URI7A:
                    // $links with 0..1 cardinality property
                    // get the response content as String
                    final EdmEntitySet targetLinkEntitySet = uriInfo.getTargetEntitySet();
                    responseHandler.onResponse(
                        (T)
                            EntityProvider.readLink(
                                getContentType(),
                                targetLinkEntitySet,
                                result.getEntity().getContent()));
                    break;

                  case URI7B:
                    // $links with * cardinality property
                    // get the response content as java.util.List<String>
                    final EdmEntitySet targetLinksEntitySet = uriInfo.getTargetEntitySet();
                    responseHandler.onResponse(
                        (T)
                            EntityProvider.readLinks(
                                getContentType(),
                                targetLinksEntitySet,
                                result.getEntity().getContent()));
                    break;

                  case URI1:
                  case URI2:
                  case URI6A:
                  case URI6B:
                    // Entity
                    // get the response content as an ODataEntry object
                    responseHandler.onResponse(
                        (T)
                            EntityProvider.readEntry(
                                response.getContentHeader(),
                                uriInfo.getTargetEntitySet(),
                                result.getEntity().getContent(),
                                EntityProviderReadProperties.init().build()));
                    break;

                  default:
                    throw new ODataApplicationException(
                        "Unsupported resource type " + uriInfo.getTargetType(), Locale.ENGLISH);
                }
              }
            }
          });
    } catch (ODataException e) {
      responseHandler.onException(e);
    } catch (URISyntaxException e) {
      responseHandler.onException(e);
    } catch (UnsupportedEncodingException e) {
      responseHandler.onException(e);
    } catch (IOException e) {
      responseHandler.onException(e);
    }
  }

  private ODataResponse writeContent(Edm edm, UriInfoWithType uriInfo, Object content)
      throws ODataApplicationException, EdmException, EntityProviderException, URISyntaxException,
          IOException {

    String responseContentType = getContentType();
    ODataResponse response;

    switch (uriInfo.getUriType()) {
      case URI4:
      case URI5:
        // simple property
        final List<EdmProperty> simplePropertyPath = uriInfo.getPropertyPath();
        final EdmProperty simpleProperty = simplePropertyPath.get(simplePropertyPath.size() - 1);
        responseContentType = simpleProperty.getMimeType();
        if (uriInfo.isValue()) {
          response = EntityProvider.writePropertyValue(simpleProperty, content);
          responseContentType = TEXT_PLAIN_WITH_CS_UTF_8.toString();
        } else {
          response = EntityProvider.writeProperty(getContentType(), simpleProperty, content);
        }
        break;

      case URI3:
        // complex property
        final List<EdmProperty> complexPropertyPath = uriInfo.getPropertyPath();
        final EdmProperty complexProperty = complexPropertyPath.get(complexPropertyPath.size() - 1);
        response = EntityProvider.writeProperty(responseContentType, complexProperty, content);
        break;

      case URI7A:
        // $links with 0..1 cardinality property
        final EdmEntitySet targetLinkEntitySet = uriInfo.getTargetEntitySet();
        EntityProviderWriteProperties linkProperties =
            EntityProviderWriteProperties.serviceRoot(new URI(serviceUri + SEPARATOR)).build();
        @SuppressWarnings("unchecked")
        final Map<String, Object> linkMap = (Map<String, Object>) content;
        response =
            EntityProvider.writeLink(
                responseContentType, targetLinkEntitySet, linkMap, linkProperties);
        break;

      case URI7B:
        // $links with * cardinality property
        final EdmEntitySet targetLinksEntitySet = uriInfo.getTargetEntitySet();
        EntityProviderWriteProperties linksProperties =
            EntityProviderWriteProperties.serviceRoot(new URI(serviceUri + SEPARATOR)).build();
        @SuppressWarnings("unchecked")
        final List<Map<String, Object>> linksMap = (List<Map<String, Object>>) content;
        response =
            EntityProvider.writeLinks(
                responseContentType, targetLinksEntitySet, linksMap, linksProperties);
        break;

      case URI1:
      case URI2:
      case URI6A:
      case URI6B:
        // Entity
        final EdmEntitySet targetEntitySet = uriInfo.getTargetEntitySet();
        EntityProviderWriteProperties properties =
            EntityProviderWriteProperties.serviceRoot(new URI(serviceUri + SEPARATOR)).build();
        @SuppressWarnings("unchecked")
        final Map<String, Object> objectMap = (Map<String, Object>) content;
        response =
            EntityProvider.writeEntry(responseContentType, targetEntitySet, objectMap, properties);
        break;

      case URI9:
        // $batch
        @SuppressWarnings("unchecked")
        final List<Olingo2BatchRequest> batchParts = (List<Olingo2BatchRequest>) content;
        response = parseBatchRequest(edm, batchParts);
        break;

      default:
        // notify exception and return!!!
        throw new ODataApplicationException(
            "Unsupported resource type " + uriInfo.getTargetType(), Locale.ENGLISH);
    }

    return response.getContentHeader() != null
        ? response
        : ODataResponse.fromResponse(response).contentHeader(responseContentType).build();
  }

  private ODataResponse parseBatchRequest(final Edm edm, final List<Olingo2BatchRequest> batchParts)
      throws IOException, EntityProviderException, ODataApplicationException, EdmException,
          URISyntaxException {

    // create Batch request from parts
    final ArrayList<BatchPart> parts = new ArrayList<BatchPart>();
    final ArrayList<BatchChangeSetPart> changeSetParts = new ArrayList<BatchChangeSetPart>();

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

    for (Olingo2BatchRequest batchPart : batchParts) {

      if (batchPart instanceof Olingo2BatchQueryRequest) {

        // need to add change set parts collected so far??
        if (!changeSetParts.isEmpty()) {
          addChangeSetParts(parts, changeSetParts);
          changeSetParts.clear();
          contentIdMap.clear();
        }

        // add to request parts
        final UriInfoWithType uriInfo = parseUri(edm, batchPart.getResourcePath(), null);
        parts.add(createBatchQueryPart(uriInfo, (Olingo2BatchQueryRequest) batchPart));

      } else {

        // add to change set parts
        final BatchChangeSetPart changeSetPart =
            createBatchChangeSetPart(edm, contentIdMap, (Olingo2BatchChangeRequest) batchPart);
        changeSetParts.add(changeSetPart);
      }
    }

    // add any remaining change set parts
    if (!changeSetParts.isEmpty()) {
      addChangeSetParts(parts, changeSetParts);
    }

    final String boundary = BOUNDARY_PREFIX + UUID.randomUUID();
    InputStream batchRequest = EntityProvider.writeBatchRequest(parts, boundary);
    // two blank lines are already added. No need to add extra blank lines
    final String contentHeader = BATCH_CONTENT_TYPE + BOUNDARY_PARAMETER + boundary;
    return ODataResponse.entity(batchRequest).contentHeader(contentHeader).build();
  }

  private void addChangeSetParts(
      ArrayList<BatchPart> parts, ArrayList<BatchChangeSetPart> changeSetParts) {
    final BatchChangeSet changeSet = BatchChangeSet.newBuilder().build();
    for (BatchChangeSetPart changeSetPart : changeSetParts) {
      changeSet.add(changeSetPart);
    }
    parts.add(changeSet);
  }

  private BatchChangeSetPart createBatchChangeSetPart(
      Edm edm, Map<String, String> contentIdMap, Olingo2BatchChangeRequest batchRequest)
      throws EdmException, URISyntaxException, EntityProviderException, IOException,
          ODataApplicationException {

    // build body string
    String resourcePath = batchRequest.getResourcePath();
    // is it a referenced entity?
    if (resourcePath.startsWith("$")) {
      resourcePath = replaceContentId(edm, resourcePath, contentIdMap);
    }

    final UriInfoWithType uriInfo = parseUri(edm, resourcePath, null);

    // serialize data into ODataResponse object, if set in request and this is not a DELETE request
    final Map<String, String> headers = new HashMap<String, String>();
    byte[] body = null;

    if (batchRequest.getBody() != null && !Operation.DELETE.equals(batchRequest.getOperation())) {

      final ODataResponse response = writeContent(edm, uriInfo, batchRequest.getBody());
      // copy response headers
      for (String header : response.getHeaderNames()) {
        headers.put(header, response.getHeader(header));
      }

      // get (http) entity which is for default Olingo2 implementation an InputStream
      body =
          response.getEntity() instanceof InputStream
              ? EntityProvider.readBinary((InputStream) response.getEntity())
              : null;
      if (body != null) {
        headers.put(HttpHeaders.CONTENT_LENGTH, String.valueOf(body.length));
      }
    }

    // Olingo is sensitive to batch part charset case!!
    headers.put(HttpHeaders.ACCEPT, getResourceContentType(uriInfo).toString().toLowerCase());
    if (!headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
      headers.put(HttpHeaders.CONTENT_TYPE, getContentType());
    }

    // add request headers
    headers.putAll(batchRequest.getHeaders());

    final String contentId = batchRequest.getContentId();
    if (contentId != null) {
      contentIdMap.put("$" + contentId, resourcePath);
    }
    return BatchChangeSetPart.uri(createBatchUri(batchRequest))
        .method(batchRequest.getOperation().getHttpMethod())
        .contentId(contentId)
        .headers(headers)
        .body(body == null ? null : new String(body, Consts.UTF_8))
        .build();
  }

  private BatchQueryPart createBatchQueryPart(
      UriInfoWithType uriInfo, Olingo2BatchQueryRequest batchRequest) {

    final Map<String, String> headers = new HashMap<String, String>(batchRequest.getHeaders());
    if (!headers.containsKey(HttpHeaders.ACCEPT)) {
      // Olingo is sensitive to batch part charset case!!
      headers.put(HttpHeaders.ACCEPT, getResourceContentType(uriInfo).toString().toLowerCase());
    }

    return BatchQueryPart.method("GET").uri(createBatchUri(batchRequest)).headers(headers).build();
  }

  private static String replaceContentId(
      Edm edm, String entityReference, Map<String, String> contentIdMap) throws EdmException {
    final int pathSeparator = entityReference.indexOf('/');
    final StringBuilder referencedEntity;
    if (pathSeparator == -1) {
      referencedEntity = new StringBuilder(contentIdMap.get(entityReference));
    } else {
      referencedEntity =
          new StringBuilder(contentIdMap.get(entityReference.substring(0, pathSeparator)));
    }

    // create a dummy entity location by adding a dummy key predicate
    // look for a Container name if available
    String referencedEntityName = referencedEntity.toString();
    final int containerSeparator = referencedEntityName.lastIndexOf('.');
    final EdmEntityContainer entityContainer;
    if (containerSeparator != -1) {
      final String containerName = referencedEntityName.substring(0, containerSeparator);
      referencedEntityName = referencedEntityName.substring(containerSeparator + 1);
      entityContainer = edm.getEntityContainer(containerName);
      if (entityContainer == null) {
        throw new IllegalArgumentException("EDM does not have entity container " + containerName);
      }
    } else {
      entityContainer = edm.getDefaultEntityContainer();
      if (entityContainer == null) {
        throw new IllegalArgumentException(
            "EDM does not have a default entity container"
                + ", use a fully qualified entity set name");
      }
    }
    final EdmEntitySet entitySet = entityContainer.getEntitySet(referencedEntityName);
    final List<EdmProperty> keyProperties = entitySet.getEntityType().getKeyProperties();

    if (keyProperties.size() == 1) {
      referencedEntity.append("('dummy')");
    } else {
      referencedEntity.append("(");
      for (EdmProperty keyProperty : keyProperties) {
        referencedEntity.append(keyProperty.getName()).append('=').append("'dummy',");
      }
      referencedEntity.deleteCharAt(referencedEntity.length() - 1);
      referencedEntity.append(')');
    }

    return pathSeparator == -1
        ? referencedEntityName
        : referencedEntity.append(entityReference.substring(pathSeparator)).toString();
  }

  private Olingo2BatchResponse parseResponse(
      Edm edm,
      Map<String, String> contentIdLocationMap,
      Olingo2BatchRequest request,
      BatchSingleResponse response)
      throws EntityProviderException, ODataApplicationException {

    // validate HTTP status
    final int statusCode = Integer.parseInt(response.getStatusCode());
    final String statusInfo = response.getStatusInfo();

    final BasicHttpResponse httpResponse =
        new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, statusCode, statusInfo));
    final Map<String, String> headers = response.getHeaders();
    for (Map.Entry<String, String> entry : headers.entrySet()) {
      httpResponse.setHeader(entry.getKey(), entry.getValue());
    }

    ByteArrayInputStream content = null;
    try {
      if (response.getBody() != null) {
        final ContentType partContentType =
            receiveWithCharsetParameter(
                ContentType.parse(headers.get(HttpHeaders.CONTENT_TYPE)), Consts.UTF_8);
        final String charset = partContentType.getCharset().toString();

        final String body = response.getBody();
        content = body != null ? new ByteArrayInputStream(body.getBytes(charset)) : null;

        httpResponse.setEntity(new StringEntity(body, charset));
      }

      AbstractFutureCallback.checkStatus(httpResponse);
    } catch (ODataApplicationException e) {
      return new Olingo2BatchResponse(
          statusCode, statusInfo, response.getContentId(), response.getHeaders(), e);
    } catch (UnsupportedEncodingException e) {
      return new Olingo2BatchResponse(
          statusCode, statusInfo, response.getContentId(), response.getHeaders(), e);
    }

    // resolve resource path and query params and parse batch part uri
    final String resourcePath = request.getResourcePath();
    final String resolvedResourcePath;
    if (resourcePath.startsWith("$")
        && !(METADATA.equals(resourcePath) || BATCH.equals(resourcePath))) {
      resolvedResourcePath = findLocation(resourcePath, contentIdLocationMap);
    } else {
      final String resourceLocation = response.getHeader(HttpHeaders.LOCATION);
      resolvedResourcePath =
          resourceLocation != null ? resourceLocation.substring(serviceUri.length()) : resourcePath;
    }
    final Map<String, String> resolvedQueryParams =
        request instanceof Olingo2BatchQueryRequest
            ? ((Olingo2BatchQueryRequest) request).getQueryParams()
            : null;
    final UriInfoWithType uriInfo = parseUri(edm, resolvedResourcePath, resolvedQueryParams);

    // resolve response content
    final Object resolvedContent = content != null ? readContent(uriInfo, content) : null;

    return new Olingo2BatchResponse(
        statusCode, statusInfo, response.getContentId(), response.getHeaders(), resolvedContent);
  }

  private ContentType receiveWithCharsetParameter(ContentType contentType, Charset charset) {
    if (contentType.getCharset() != null) {
      return contentType;
    }
    final String mimeType = contentType.getMimeType();
    if (mimeType.equals(ContentType.TEXT_PLAIN.getMimeType())
        || AbstractFutureCallback.ODATA_MIME_TYPE.matcher(mimeType).matches()) {
      return contentType.withCharset(charset);
    }
    return contentType;
  }

  private String findLocation(String resourcePath, Map<String, String> contentIdLocationMap) {
    final int pathSeparator = resourcePath.indexOf('/');
    if (pathSeparator == -1) {
      return contentIdLocationMap.get(resourcePath);
    } else {
      return contentIdLocationMap.get(resourcePath.substring(0, pathSeparator))
          + resourcePath.substring(pathSeparator);
    }
  }

  private String createBatchUri(Olingo2BatchRequest part) {
    String result;
    if (part instanceof Olingo2BatchQueryRequest) {
      final Olingo2BatchQueryRequest queryPart = (Olingo2BatchQueryRequest) part;
      result = createUri(queryPart.getResourcePath(), queryPart.getQueryParams());
    } else {
      result = createUri(part.getResourcePath());
    }
    // strip base URI
    return result.substring(serviceUri.length() + 1);
  }

  private String createUri(String resourcePath) {
    return createUri(resourcePath, null);
  }

  private String createUri(String resourcePath, Map<String, String> queryParams) {

    final StringBuilder absolutUri =
        new StringBuilder(serviceUri).append(SEPARATOR).append(resourcePath);
    if (queryParams != null && !queryParams.isEmpty()) {
      absolutUri.append("/?");
      int nParams = queryParams.size();
      int index = 0;
      for (Map.Entry<String, String> entry : queryParams.entrySet()) {
        absolutUri.append(entry.getKey()).append('=').append(entry.getValue());
        if (++index < nParams) {
          absolutUri.append('&');
        }
      }
    }
    return absolutUri.toString();
  }

  private static UriInfoWithType parseUri(
      Edm edm, String resourcePath, Map<String, String> queryParams) {
    UriInfoWithType result;
    try {
      final List<PathSegment> pathSegments = new ArrayList<PathSegment>();
      final String[] segments = new URI(resourcePath).getPath().split(SEPARATOR);
      if (queryParams == null) {
        queryParams = Collections.emptyMap();
      }
      for (String segment : segments) {
        if (segment.indexOf(';') == -1) {

          pathSegments.add(new ODataPathSegmentImpl(segment, null));
        } else {

          // handle matrix params in path segment
          final String[] splitSegment = segment.split(";");
          segment = splitSegment[0];

          Map<String, List<String>> matrixParams = new HashMap<String, List<String>>();
          for (int i = 1; i < splitSegment.length; i++) {
            final String[] param = splitSegment[i].split("=");
            List<String> values = matrixParams.get(param[0]);
            if (values == null) {
              values = new ArrayList<String>();
              matrixParams.put(param[0], values);
            }
            if (param[1].indexOf(',') == -1) {
              values.add(param[1]);
            } else {
              values.addAll(Arrays.asList(param[1].split(",")));
            }
          }
          pathSegments.add(new ODataPathSegmentImpl(segment, matrixParams));
        }
      }
      result = new UriInfoWithType(UriParser.parse(edm, pathSegments, queryParams), resourcePath);
    } catch (URISyntaxException e) {
      throw new IllegalArgumentException("resourcePath: " + e.getMessage(), e);
    } catch (ODataException e) {
      throw new IllegalArgumentException("resourcePath: " + e.getMessage(), e);
    }

    return result;
  }

  /** public for unit test, not to be used otherwise */
  public void execute(
      HttpUriRequest httpUriRequest,
      ContentType contentType,
      FutureCallback<HttpResponse> callback) {

    // add accept header when its not a form or multipart
    final String contentTypeString = contentType.toString();
    if (!ContentType.APPLICATION_FORM_URLENCODED.getMimeType().equals(contentType.getMimeType())
        && !contentType.getMimeType().startsWith(MULTIPART_MIME_TYPE)) {
      // otherwise accept what is being sent
      httpUriRequest.addHeader(HttpHeaders.ACCEPT, contentTypeString);
    }
    // is something being sent?
    if (httpUriRequest instanceof HttpEntityEnclosingRequestBase
        && httpUriRequest.getFirstHeader(HttpHeaders.CONTENT_TYPE) == null) {
      httpUriRequest.addHeader(HttpHeaders.CONTENT_TYPE, contentTypeString);
    }

    // set user specified custom headers
    if (httpHeaders != null && !httpHeaders.isEmpty()) {
      for (Map.Entry<String, String> entry : httpHeaders.entrySet()) {
        httpUriRequest.setHeader(entry.getKey(), entry.getValue());
      }
    }

    // add client protocol version if not specified
    if (!httpUriRequest.containsHeader(ODataHttpHeaders.DATASERVICEVERSION)) {
      httpUriRequest.addHeader(ODataHttpHeaders.DATASERVICEVERSION, ODataServiceVersion.V20);
    }
    if (!httpUriRequest.containsHeader(MAX_DATA_SERVICE_VERSION)) {
      httpUriRequest.addHeader(MAX_DATA_SERVICE_VERSION, ODataServiceVersion.V30);
    }

    // execute request
    client.execute(httpUriRequest, callback);
  }
}