protected static void appendPropertyValue(
     final JsonStreamWriter jsonStreamWriter,
     final EntityPropertyInfo propertyInfo,
     final Object value)
     throws IOException, EdmException, EntityProviderException {
   if (propertyInfo.isComplex()) {
     if (value == null || value instanceof Map<?, ?>) {
       jsonStreamWriter.beginObject();
       appendPropertyMetadata(jsonStreamWriter, propertyInfo.getType());
       for (final EntityPropertyInfo childPropertyInfo :
           ((EntityComplexPropertyInfo) propertyInfo).getPropertyInfos()) {
         jsonStreamWriter.separator();
         final String name = childPropertyInfo.getName();
         jsonStreamWriter.name(name);
         appendPropertyValue(
             jsonStreamWriter,
             childPropertyInfo,
             value == null ? null : ((Map<?, ?>) value).get(name));
       }
       jsonStreamWriter.endObject();
     } else {
       throw new EntityProviderException(
           EntityProviderException.ILLEGAL_ARGUMENT.addContent(
               "A complex property must have a Map as data"));
     }
   } else {
     final EdmSimpleType type = (EdmSimpleType) propertyInfo.getType();
     final Object contentValue =
         value instanceof Map ? ((Map<?, ?>) value).get(propertyInfo.getName()) : value;
     final String valueAsString =
         type.valueToString(contentValue, EdmLiteralKind.JSON, propertyInfo.getFacets());
     switch (EdmSimpleTypeKind.valueOf(type.getName())) {
       case String:
         jsonStreamWriter.stringValue(valueAsString);
         break;
       case Boolean:
       case Byte:
       case SByte:
       case Int16:
       case Int32:
         jsonStreamWriter.unquotedValue(valueAsString);
         break;
       case DateTime:
       case DateTimeOffset:
         // Although JSON escaping is (and should be) done in the JSON
         // serializer, we backslash-escape the forward slash here explicitly
         // because it is not required to escape it in JSON but in OData.
         jsonStreamWriter.stringValueRaw(
             valueAsString == null ? null : valueAsString.replace("/", "\\/"));
         break;
       default:
         jsonStreamWriter.stringValueRaw(valueAsString);
         break;
     }
   }
 }
  public void append(final Writer writer, final EntityPropertyInfo propertyInfo, final List<?> data)
      throws EntityProviderException {
    JsonStreamWriter jsonStreamWriter = new JsonStreamWriter(writer);

    try {
      jsonStreamWriter.beginObject();
      jsonStreamWriter.name(FormatJson.D);
      jsonStreamWriter.beginObject();

      jsonStreamWriter.name(FormatJson.METADATA);
      jsonStreamWriter.beginObject();
      jsonStreamWriter.namedStringValueRaw(
          FormatJson.TYPE,
          "Collection("
              + propertyInfo.getType().getNamespace()
              + Edm.DELIMITER
              + propertyInfo.getType().getName()
              + ")");
      jsonStreamWriter.endObject();
      jsonStreamWriter.separator();

      jsonStreamWriter.name(FormatJson.RESULTS);
      jsonStreamWriter.beginArray();
      boolean first = true;
      for (final Object item : data) {
        if (first) {
          first = false;
        } else {
          jsonStreamWriter.separator();
        }
        JsonPropertyEntityProducer.appendPropertyValue(jsonStreamWriter, propertyInfo, item);
      }
      jsonStreamWriter.endArray();

      jsonStreamWriter.endObject();
      jsonStreamWriter.endObject();
    } catch (final IOException e) {
      throw new EntityProviderException(EntityProviderException.COMMON, e);
    } catch (final EdmException e) {
      throw new EntityProviderException(EntityProviderException.COMMON, e);
    }
  }
  public void append(final Writer writer, final EntityPropertyInfo propertyInfo, final Object value)
      throws EntityProviderException {
    JsonStreamWriter jsonStreamWriter = new JsonStreamWriter(writer);

    try {
      jsonStreamWriter.beginObject().name(FormatJson.D).beginObject();

      jsonStreamWriter.name(propertyInfo.getName());
      appendPropertyValue(
          jsonStreamWriter,
          propertyInfo.isComplex() ? (EntityComplexPropertyInfo) propertyInfo : propertyInfo,
          value);

      jsonStreamWriter.endObject().endObject();
    } catch (final IOException e) {
      throw new EntityProviderException(
          EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
    } catch (final EdmException e) {
      throw new EntityProviderException(
          EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
    }
  }
  public void append(
      final Writer writer,
      final EntityInfoAggregator entityInfo,
      final Map<String, Object> data,
      final boolean isRootElement)
      throws EntityProviderException {
    final EdmEntityType type = entityInfo.getEntityType();

    try {
      jsonStreamWriter = new JsonStreamWriter(writer);
      if (isRootElement) {
        jsonStreamWriter.beginObject();
        jsonStreamWriter.name(FormatJson.D);
      }

      jsonStreamWriter.beginObject();

      jsonStreamWriter.name(FormatJson.METADATA);
      jsonStreamWriter.beginObject();
      final String self = AtomEntryEntityProducer.createSelfLink(entityInfo, data, null);
      location = properties.getServiceRoot().toASCIIString() + self;
      jsonStreamWriter.namedStringValue(FormatJson.ID, location);
      jsonStreamWriter.separator();
      jsonStreamWriter.namedStringValue(FormatJson.URI, location);
      jsonStreamWriter.separator();
      jsonStreamWriter.namedStringValueRaw(
          FormatJson.TYPE, type.getNamespace() + Edm.DELIMITER + type.getName());
      eTag = AtomEntryEntityProducer.createETag(entityInfo, data);
      if (eTag != null) {
        jsonStreamWriter.separator();
        jsonStreamWriter.namedStringValue(FormatJson.ETAG, eTag);
      }
      if (type.hasStream()) {
        jsonStreamWriter.separator();
        jsonStreamWriter.namedStringValueRaw(
            FormatJson.CONTENT_TYPE,
            properties.getMediaResourceMimeType() == null
                ? type.getMapping() == null || type.getMapping().getMimeType() == null
                    ? HttpContentType.APPLICATION_OCTET_STREAM
                    : data.get(type.getMapping().getMimeType()).toString()
                : properties.getMediaResourceMimeType());
        jsonStreamWriter.separator();
        jsonStreamWriter.namedStringValue(FormatJson.MEDIA_SRC, self + "/$value");
        jsonStreamWriter.separator();
        jsonStreamWriter.namedStringValue(FormatJson.EDIT_MEDIA, location + "/$value");
      }
      jsonStreamWriter.endObject();

      for (final String propertyName : type.getPropertyNames()) {
        if (entityInfo.getSelectedPropertyNames().contains(propertyName)) {
          jsonStreamWriter.separator();
          jsonStreamWriter.name(propertyName);
          JsonPropertyEntityProducer.appendPropertyValue(
              jsonStreamWriter, entityInfo.getPropertyInfo(propertyName), data.get(propertyName));
        }
      }

      for (final String navigationPropertyName : type.getNavigationPropertyNames()) {
        if (entityInfo.getSelectedNavigationPropertyNames().contains(navigationPropertyName)) {
          jsonStreamWriter.separator();
          jsonStreamWriter.name(navigationPropertyName);
          if (entityInfo.getExpandedNavigationPropertyNames().contains(navigationPropertyName)) {
            if (properties.getCallbacks() != null
                && properties.getCallbacks().containsKey(navigationPropertyName)) {
              final EdmNavigationProperty navigationProperty =
                  (EdmNavigationProperty) type.getProperty(navigationPropertyName);
              final boolean isFeed = navigationProperty.getMultiplicity() == EdmMultiplicity.MANY;
              final EdmEntitySet entitySet = entityInfo.getEntitySet();
              final EdmEntitySet inlineEntitySet =
                  entitySet.getRelatedEntitySet(navigationProperty);

              WriteCallbackContext context =
                  isFeed ? new WriteFeedCallbackContext() : new WriteEntryCallbackContext();
              context.setSourceEntitySet(entitySet);
              context.setNavigationProperty(navigationProperty);
              context.setEntryData(data);
              context.setCurrentExpandSelectTreeNode(
                  properties.getExpandSelectTree().getLinks().get(navigationPropertyName));

              ODataCallback callback = properties.getCallbacks().get(navigationPropertyName);
              try {
                if (isFeed) {
                  final WriteFeedCallbackResult result =
                      ((OnWriteFeedContent) callback)
                          .retrieveFeedResult((WriteFeedCallbackContext) context);
                  List<Map<String, Object>> inlineData = result.getFeedData();
                  if (inlineData != null) {
                    final EntityProviderWriteProperties inlineProperties =
                        result.getInlineProperties();
                    final EntityInfoAggregator inlineEntityInfo =
                        EntityInfoAggregator.create(
                            inlineEntitySet, inlineProperties.getExpandSelectTree());
                    new JsonFeedEntityProducer(inlineProperties)
                        .append(writer, inlineEntityInfo, inlineData, false);
                  }
                } else {
                  final WriteEntryCallbackResult result =
                      ((OnWriteEntryContent) callback)
                          .retrieveEntryResult((WriteEntryCallbackContext) context);
                  Map<String, Object> inlineData = result.getEntryData();
                  if (inlineData != null) {
                    final EntityProviderWriteProperties inlineProperties =
                        result.getInlineProperties();
                    final EntityInfoAggregator inlineEntityInfo =
                        EntityInfoAggregator.create(
                            inlineEntitySet, inlineProperties.getExpandSelectTree());
                    new JsonEntryEntityProducer(inlineProperties)
                        .append(writer, inlineEntityInfo, inlineData, false);
                  }
                }
              } catch (final ODataApplicationException e) {
                throw new EntityProviderException(EntityProviderException.COMMON, e);
              }
            } else {
              throw new EntityProviderException(
                  EntityProviderException.EXPANDNOTSUPPORTED.addContent(navigationPropertyName));
            }
          } else {
            jsonStreamWriter.beginObject();
            jsonStreamWriter.name(FormatJson.DEFERRED);
            JsonLinkEntityProducer.appendUri(
                jsonStreamWriter, location + "/" + Encoder.encode(navigationPropertyName));
            jsonStreamWriter.endObject();
          }
        }
      }

      jsonStreamWriter.endObject();

      if (isRootElement) {
        jsonStreamWriter.endObject();
      }

    } catch (final IOException e) {
      throw new EntityProviderException(EntityProviderException.COMMON, e);
    } catch (final EdmException e) {
      throw new EntityProviderException(EntityProviderException.COMMON, e);
    }
  }