blob: c011fdd8269df439c8d96852c573197315115928 [file] [log] [blame]
/*
* 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.server.core.serializer.json;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.URI;
import java.util.List;
import org.apache.olingo.commons.api.Constants;
import org.apache.olingo.commons.api.IConstants;
import org.apache.olingo.commons.api.constants.Constantsv00;
import org.apache.olingo.commons.api.data.AbstractEntityCollection;
import org.apache.olingo.commons.api.data.AbstractODataObject;
import org.apache.olingo.commons.api.data.Annotatable;
import org.apache.olingo.commons.api.data.Annotation;
import org.apache.olingo.commons.api.data.ComplexValue;
import org.apache.olingo.commons.api.data.ContextURL;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.Link;
import org.apache.olingo.commons.api.data.Linked;
import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.data.Valuable;
import org.apache.olingo.commons.api.data.ValueType;
import org.apache.olingo.commons.api.edm.EdmComplexType;
import org.apache.olingo.commons.api.edm.EdmEntityType;
import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.edm.EdmType;
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.server.api.ServiceMetadata;
import org.apache.olingo.server.api.serializer.EdmAssistedSerializer;
import org.apache.olingo.server.api.serializer.EdmAssistedSerializerOptions;
import org.apache.olingo.server.api.serializer.SerializerException;
import org.apache.olingo.server.api.serializer.SerializerException.MessageKeys;
import org.apache.olingo.server.api.serializer.SerializerResult;
import org.apache.olingo.server.core.serializer.SerializerResultImpl;
import org.apache.olingo.server.core.serializer.utils.CircleStreamBuffer;
import org.apache.olingo.server.core.serializer.utils.ContentTypeHelper;
import org.apache.olingo.server.core.serializer.utils.ContextURLBuilder;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
public class EdmAssistedJsonSerializer implements EdmAssistedSerializer {
private static final String IO_EXCEPTION_TEXT = "An I/O exception occurred.";
protected final boolean isIEEE754Compatible;
protected final boolean isODataMetadataNone;
protected final boolean isODataMetadataFull;
private IConstants constants;
public EdmAssistedJsonSerializer(final ContentType contentType) {
this.isIEEE754Compatible = ContentTypeHelper.isODataIEEE754Compatible(contentType);
this.isODataMetadataNone = ContentTypeHelper.isODataMetadataNone(contentType);
this.isODataMetadataFull = ContentTypeHelper.isODataMetadataFull(contentType);
this.constants = new Constantsv00();
}
public EdmAssistedJsonSerializer(final ContentType contentType, final IConstants constants) {
this.isIEEE754Compatible = ContentTypeHelper.isODataIEEE754Compatible(contentType);
this.isODataMetadataNone = ContentTypeHelper.isODataMetadataNone(contentType);
this.isODataMetadataFull = ContentTypeHelper.isODataMetadataFull(contentType);
this.constants = constants;
}
@Override
public SerializerResult entityCollection(final ServiceMetadata metadata, final EdmEntityType entityType,
final AbstractEntityCollection entityCollection, final EdmAssistedSerializerOptions options)
throws SerializerException {
return serialize(metadata, entityType, entityCollection, options == null ? null : options.getContextURL());
}
public SerializerResult entity(final ServiceMetadata metadata, final EdmEntityType entityType, final Entity entity,
final EdmAssistedSerializerOptions options) throws SerializerException {
return serialize(metadata, entityType, entity, options == null ? null : options.getContextURL());
}
protected SerializerResult serialize(final ServiceMetadata metadata, final EdmEntityType entityType,
final AbstractODataObject obj, final ContextURL contextURL) throws SerializerException {
final String metadataETag =
isODataMetadataNone || metadata == null || metadata.getServiceMetadataETagSupport() == null ? null : metadata
.getServiceMetadataETagSupport().getMetadataETag();
final String contextURLString = isODataMetadataNone || contextURL == null ? null : ContextURLBuilder.create(
contextURL).toASCIIString();
OutputStream outputStream = null;
SerializerException cachedException = null;
CircleStreamBuffer buffer = new CircleStreamBuffer();
outputStream = buffer.getOutputStream();
try (JsonGenerator json = new JsonFactory().createGenerator(outputStream)) {
if (obj instanceof AbstractEntityCollection) {
doSerialize(entityType, (AbstractEntityCollection) obj, contextURLString, metadataETag, json);
} else if (obj instanceof Entity) {
doSerialize(entityType, (Entity) obj, contextURLString, metadataETag, json);
} else {
throw new SerializerException("Input type not supported.", MessageKeys.NOT_IMPLEMENTED);
}
json.flush();
json.close();
return SerializerResultImpl.with().content(buffer.getInputStream()).build();
} catch (final IOException e) {
cachedException = new SerializerException(IO_EXCEPTION_TEXT, e, SerializerException.MessageKeys.IO_EXCEPTION);
throw cachedException;
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (final IOException e) {
throw cachedException == null ? new SerializerException(IO_EXCEPTION_TEXT, e,
SerializerException.MessageKeys.IO_EXCEPTION) : cachedException;
}
}
}
}
protected void doSerialize(final EdmEntityType entityType, final AbstractEntityCollection entityCollection,
final String contextURLString, final String metadataETag, JsonGenerator json)
throws IOException, SerializerException {
json.writeStartObject();
metadata(contextURLString, metadataETag, null, null, entityCollection.getId(), false, json);
if (entityCollection.getCount() != null) {
if (isIEEE754Compatible) {
json.writeStringField(constants.getCount(), Integer.toString(entityCollection.getCount()));
} else {
json.writeNumberField(constants.getCount(), entityCollection.getCount());
}
}
if (entityCollection.getDeltaLink() != null) {
json.writeStringField(constants.getDeltaLink(), entityCollection.getDeltaLink().toASCIIString());
}
for (final Annotation annotation : entityCollection.getAnnotations()) {
valuable(json, annotation, '@' + annotation.getTerm(), null, null);
}
json.writeArrayFieldStart(Constants.VALUE);
for (final Entity entity : entityCollection) {
doSerialize(entityType, entity, null, null, json);
}
json.writeEndArray();
if (entityCollection.getNext() != null) {
json.writeStringField(constants.getNextLink(), entityCollection.getNext().toASCIIString());
}
json.writeEndObject();
}
protected void doSerialize(final EdmEntityType entityType, final Entity entity,
final String contextURLString, final String metadataETag, JsonGenerator json)
throws IOException, SerializerException {
json.writeStartObject();
final String typeName = entity.getType() == null ? null : new EdmTypeInfo.Builder().setTypeExpression(entity
.getType()).build().external();
metadata(contextURLString, metadataETag, entity.getETag(), typeName, entity.getId(), true, json);
for (final Annotation annotation : entity.getAnnotations()) {
valuable(json, annotation, '@' + annotation.getTerm(), null, null);
}
for (final Property property : entity.getProperties()) {
final String name = property.getName();
final EdmProperty edmProperty = entityType == null || entityType.getStructuralProperty(name) == null ? null
: entityType.getStructuralProperty(name);
valuable(json, property, name, edmProperty == null ? null : edmProperty.getType(), edmProperty);
}
if (!isODataMetadataNone &&
entity.getEditLink() != null && entity.getEditLink().getHref() != null) {
json.writeStringField(constants.getEditLink(), entity.getEditLink().getHref());
if (entity.isMediaEntity()) {
json.writeStringField(constants.getMediaReadLink(), entity.getEditLink().getHref() + "/$value");
}
}
links(entity, entityType, json);
json.writeEndObject();
}
private void metadata(final String contextURLString, final String metadataETag, final String eTag,
final String type, final URI id, final boolean writeNullId, JsonGenerator json)
throws IOException, SerializerException {
if (!isODataMetadataNone) {
if (contextURLString != null) {
json.writeStringField(constants.getContext(), contextURLString);
}
if (metadataETag != null) {
json.writeStringField(constants.getMetadataEtag(), metadataETag);
}
if (eTag != null) {
json.writeStringField(constants.getEtag(), eTag);
}
if(isODataMetadataFull){
if (type != null) {
json.writeStringField(constants.getType(), type);
}
if (id == null) {
if (writeNullId) {
json.writeNullField(constants.getId());
}
} else {
json.writeStringField(constants.getId(), id.toASCIIString());
}
}
}
}
private void links(final Linked linked, final EdmEntityType entityType, JsonGenerator json)
throws IOException, SerializerException {
for (final Link link : linked.getNavigationLinks()) {
final String name = link.getTitle();
for (final Annotation annotation : link.getAnnotations()) {
valuable(json, annotation, name + '@' + annotation.getTerm(), null, null);
}
final EdmEntityType targetType =
entityType == null || name == null || entityType.getNavigationProperty(name) == null ? null : entityType
.getNavigationProperty(name).getType();
if (link.getInlineEntity() != null) {
json.writeFieldName(name);
doSerialize(targetType, link.getInlineEntity(), null, null, json);
} else if (link.getInlineEntitySet() != null) {
json.writeArrayFieldStart(name);
for (final Entity subEntry : link.getInlineEntitySet().getEntities()) {
doSerialize(targetType, subEntry, null, null, json);
}
json.writeEndArray();
}
}
}
private void collection(final JsonGenerator json, final EdmType itemType, final String typeName,
final EdmProperty edmProperty, final ValueType valueType, final List<?> value)
throws IOException, SerializerException {
json.writeStartArray();
for (final Object item : value) {
switch (valueType) {
case COLLECTION_PRIMITIVE:
primitiveValue(json, (EdmPrimitiveType) itemType, typeName, edmProperty, item);
break;
case COLLECTION_GEOSPATIAL:
case COLLECTION_ENUM:
throw new SerializerException("Geo and enum types are not supported.", MessageKeys.NOT_IMPLEMENTED);
case COLLECTION_COMPLEX:
complexValue(json, (EdmComplexType) itemType, typeName, (ComplexValue) item);
break;
default:
}
}
json.writeEndArray();
}
protected void primitiveValue(final JsonGenerator json, final EdmPrimitiveType valueType, final String typeName,
final EdmProperty edmProperty, final Object value) throws IOException, SerializerException {
EdmPrimitiveType type = valueType;
if (type == null) {
final EdmPrimitiveTypeKind kind =
typeName == null ? EdmTypeInfo.determineTypeKind(value) : new EdmTypeInfo.Builder().setTypeExpression(
typeName).build().getPrimitiveTypeKind();
type = kind == null ? null : EdmPrimitiveTypeFactory.getInstance(kind);
}
if (value == null) {
json.writeNull();
} else if (type == null) {
throw new SerializerException("The primitive type could not be determined.",
MessageKeys.INCONSISTENT_PROPERTY_TYPE, "");
} else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)) {
json.writeBoolean((Boolean) value);
} else {
String serialized = null;
try {
Integer scale = null;
if (value instanceof BigDecimal) {
scale = ((BigDecimal) value).scale();
} else {
scale = Constants.DEFAULT_SCALE;
}
serialized = type.valueToString(value,
edmProperty == null ? null : edmProperty.isNullable(),
edmProperty == null ? null : edmProperty.getMaxLength(),
edmProperty == null ? null : edmProperty.getPrecision(),
edmProperty == null ? scale : edmProperty.getScale(),
edmProperty == null ? null : edmProperty.isUnicode());
} catch (final EdmPrimitiveTypeException e) {
final String name = edmProperty == null ? "" : edmProperty.getName();
throw new SerializerException("Wrong value for property '" + name + "'!", e,
SerializerException.MessageKeys.WRONG_PROPERTY_VALUE, name, value.toString());
}
if (isIEEE754Compatible &&
(type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int64)
|| type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal))
|| type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Byte)
&& type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.SByte)
&& type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Single)
&& type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Double)
&& type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int16)
&& type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int32)
&& type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int64)
&& type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal)) {
json.writeString(serialized);
} else {
json.writeNumber(serialized);
}
}
}
private void complexValue(final JsonGenerator json, final EdmComplexType valueType, final String typeName,
final ComplexValue value) throws IOException, SerializerException {
json.writeStartObject();
if (typeName != null && isODataMetadataFull) {
json.writeStringField(constants.getType(), typeName);
}
for (final Property property : value.getValue()) {
final String name = property.getName();
final EdmProperty edmProperty = valueType == null || valueType.getStructuralProperty(name) == null ? null
: valueType.getStructuralProperty(name);
valuable(json, property, name, edmProperty == null ? null : edmProperty.getType(), edmProperty);
}
links(value, null, json);
json.writeEndObject();
}
private void value(final JsonGenerator json, final Valuable value, final EdmType type, final EdmProperty edmProperty)
throws IOException, SerializerException {
final String typeName = value.getType() == null ? null : new EdmTypeInfo.Builder().setTypeExpression(value
.getType()).build().external();
if (value.isNull()) {
json.writeNull();
} else if (value.isCollection()) {
collection(json, type, typeName, edmProperty, value.getValueType(), value.asCollection());
} else if (value.isPrimitive()) {
primitiveValue(json, (EdmPrimitiveType) type, typeName, edmProperty, value.asPrimitive());
} else if (value.isComplex()) {
complexValue(json, (EdmComplexType) type, typeName, value.asComplex());
} else if (value.isEnum() || value.isGeospatial()) {
throw new SerializerException("Geo and enum types are not supported.", MessageKeys.NOT_IMPLEMENTED);
}
}
protected void valuable(JsonGenerator json, final Valuable valuable, final String name, final EdmType type,
final EdmProperty edmProperty) throws IOException, SerializerException {
if (isODataMetadataFull
&& !(valuable instanceof Annotation) && !valuable.isComplex()) {
String typeName = valuable.getType();
if (typeName == null && type == null && valuable.isPrimitive()) {
if (valuable.isCollection()) {
if (!valuable.asCollection().isEmpty()) {
final EdmPrimitiveTypeKind kind = EdmTypeInfo.determineTypeKind(valuable.asCollection().get(0));
if (kind != null) {
typeName = "Collection(" + kind.getFullQualifiedName().getFullQualifiedNameAsString() + ')';
}
}
} else {
final EdmPrimitiveTypeKind kind = EdmTypeInfo.determineTypeKind(valuable.asPrimitive());
if (kind != null) {
typeName = kind.getFullQualifiedName().getFullQualifiedNameAsString();
}
}
}
if (typeName != null) {
json.writeStringField(name + constants.getType(), constructTypeExpression(typeName));
}
}
for (final Annotation annotation : ((Annotatable) valuable).getAnnotations()) {
valuable(json, annotation, name + '@' + annotation.getTerm(), null, null);
}
json.writeFieldName(name);
value(json, valuable, type, edmProperty);
}
private String constructTypeExpression(String typeName) {
EdmTypeInfo typeInfo = new EdmTypeInfo.Builder().setTypeExpression(typeName).build();
StringBuilder stringBuilder = new StringBuilder();
if (typeInfo.isCollection()) {
stringBuilder.append("#Collection(");
} else {
stringBuilder.append('#');
}
stringBuilder.append(typeInfo.isPrimitiveType() ? typeInfo.getFullQualifiedName().getName() : typeInfo
.getFullQualifiedName().getFullQualifiedNameAsString());
if (typeInfo.isCollection()) {
stringBuilder.append(')');
}
return stringBuilder.toString();
}
}