| /* |
| * 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(); |
| } |
| } |