blob: 0ac5251613b1a975bc6be9cb837461602ef1e387 [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.deserializer.json;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.AbstractMap.SimpleEntry;
import org.apache.olingo.commons.api.Constants;
import org.apache.olingo.commons.api.data.Annotation;
import org.apache.olingo.commons.api.data.ComplexValue;
import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.data.PropertyType;
import org.apache.olingo.commons.api.data.Valuable;
import org.apache.olingo.commons.api.data.ValueType;
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.geo.Geospatial;
import org.apache.olingo.commons.core.edm.EdmTypeInfo;
import org.apache.olingo.server.api.deserializer.DeserializerException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class ODataJsonInstanceAnnotationDeserializer {
private static final String ODATA_CONTROL_INFORMATION_PREFIX = "@odata.";
/**
* Consume the instance annotation of an entity or a property
* @param key String
* @param value JsonNode
* @return Annotation of an entity
* @throws DeserializerException
*/
public Annotation consumeInstanceAnnotation(String key, JsonNode value)
throws DeserializerException {
Annotation annotation = new Annotation();
annotation.setTerm(key);
try {
value(annotation, value);
} catch (EdmPrimitiveTypeException | IOException e) {
throw new DeserializerException("Property: " + key,
DeserializerException.MessageKeys.INVALID_VALUE_FOR_PROPERTY, key);
}
return annotation;
}
private void value(final Valuable valuable, final JsonNode node)
throws IOException, EdmPrimitiveTypeException {
EdmTypeInfo typeInfo = null;
final Map.Entry<PropertyType, EdmTypeInfo> guessed = guessPropertyType(node);
if (typeInfo == null) {
typeInfo = guessed.getValue();
}
final PropertyType propType = typeInfo == null ? guessed.getKey()
: typeInfo.isCollection() ? PropertyType.COLLECTION
: typeInfo.isPrimitiveType() ? PropertyType.PRIMITIVE
: node.isValueNode() ? PropertyType.ENUM : PropertyType.COMPLEX;
switch (propType) {
case COLLECTION:
fromCollection(valuable, node.elements(), typeInfo);
break;
case COMPLEX:
if (node.has(Constants.JSON_TYPE)) {
valuable.setType(node.get(Constants.JSON_TYPE).asText());
((ObjectNode) node).remove(Constants.JSON_TYPE);
}
final Object value = fromComplex((ObjectNode) node);
if (value instanceof ComplexValue) {
((ComplexValue) value).setTypeName(valuable.getType());
}
valuable.setValue(ValueType.COMPLEX, value);
break;
case ENUM:
if (!node.isNull()) {
valuable.setValue(ValueType.ENUM, node.asText());
}
break;
case PRIMITIVE:
if (valuable.getType() == null && typeInfo != null) {
valuable.setType(typeInfo.getFullQualifiedName().toString());
}
final Object primitiveValue = fromPrimitive(node, typeInfo);
valuable.setValue(primitiveValue instanceof Geospatial ?
ValueType.GEOSPATIAL : ValueType.PRIMITIVE,
primitiveValue);
break;
case EMPTY:
default:
valuable.setValue(ValueType.PRIMITIVE, "");
}
}
private Object fromPrimitive(final JsonNode node, final EdmTypeInfo typeInfo)
throws EdmPrimitiveTypeException {
return node.isNull() ? null
: typeInfo == null ? node.asText()
: ((EdmPrimitiveType) typeInfo.getType()).valueOfString(node.asText(), true, null,
Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, true,
((EdmPrimitiveType) typeInfo.getType()).getDefaultType());
}
private Object fromComplex(final ObjectNode node) throws IOException, EdmPrimitiveTypeException {
final ComplexValue complexValue = new ComplexValue();
final Set<String> toRemove = new HashSet<>();
for (final Iterator<Map.Entry<String, JsonNode>> itor = node.fields(); itor.hasNext();) {
final Map.Entry<String, JsonNode> field = itor.next();
if (field.getKey().contains(ODATA_CONTROL_INFORMATION_PREFIX)) {
toRemove.add(field.getKey());
} else {
Property property = new Property();
property.setName(field.getKey());
value(property, field.getValue());
complexValue.getValue().add(property);
}
}
node.remove(toRemove);
return complexValue;
}
private void fromCollection(final Valuable valuable, final Iterator<JsonNode> nodeItor,
final EdmTypeInfo typeInfo)
throws IOException, EdmPrimitiveTypeException {
final List<Object> values = new ArrayList<>();
ValueType valueType = ValueType.COLLECTION_PRIMITIVE;
final EdmTypeInfo type = typeInfo == null ? null
: new EdmTypeInfo.Builder().setTypeExpression(
typeInfo.getFullQualifiedName().toString()).build();
while (nodeItor.hasNext()) {
final JsonNode child = nodeItor.next();
if (child.isValueNode()) {
if (typeInfo == null || typeInfo.isPrimitiveType()) {
final Object value = fromPrimitive(child, type);
valueType = value instanceof Geospatial ? ValueType.COLLECTION_GEOSPATIAL
: ValueType.COLLECTION_PRIMITIVE;
values.add(value);
} else {
valueType = ValueType.COLLECTION_ENUM;
values.add(child.asText());
}
} else if (child.isContainerNode()) {
EdmTypeInfo childType = null;
if (child.has(Constants.JSON_TYPE)) {
String typeName = child.get(Constants.JSON_TYPE).asText();
childType = typeName == null ? null : new EdmTypeInfo.Builder()
.setTypeExpression(typeName).build();
((ObjectNode) child).remove(Constants.JSON_TYPE);
}
final Object value = fromComplex((ObjectNode) child);
if (childType != null) {
((ComplexValue) value).setTypeName(childType.external());
}
valueType = ValueType.COLLECTION_COMPLEX;
values.add(value);
}
}
valuable.setValue(valueType, values);
}
private Map.Entry<PropertyType, EdmTypeInfo> guessPropertyType(final JsonNode node) {
PropertyType type;
String typeExpression = null;
if (node.isValueNode() || node.isNull()) {
type = PropertyType.PRIMITIVE;
typeExpression = guessPrimitiveTypeKind(node).getFullQualifiedName().toString();
} else if (node.isArray()) {
type = PropertyType.COLLECTION;
if (node.has(0) && node.get(0).isValueNode()) {
typeExpression = "Collection(" + guessPrimitiveTypeKind(node.get(0)) + ')';
}
} else if (node.isObject()) {
if (node.has(Constants.ATTR_TYPE)) {
type = PropertyType.PRIMITIVE;
typeExpression = "Edm.Geography" + node.get(Constants.ATTR_TYPE).asText();
} else {
type = PropertyType.COMPLEX;
}
} else {
type = PropertyType.EMPTY;
}
final EdmTypeInfo typeInfo = typeExpression == null ? null
: new EdmTypeInfo.Builder().setTypeExpression(typeExpression).build();
return new SimpleEntry<>(type, typeInfo);
}
private EdmPrimitiveTypeKind guessPrimitiveTypeKind(final JsonNode node) {
return node.isShort() ? EdmPrimitiveTypeKind.Int16
: node.isInt() ? EdmPrimitiveTypeKind.Int32
: node.isLong() ? EdmPrimitiveTypeKind.Int64
: node.isBoolean() ? EdmPrimitiveTypeKind.Boolean
: node.isFloat() ? EdmPrimitiveTypeKind.Single
: node.isDouble() ? EdmPrimitiveTypeKind.Double
: node.isBigDecimal() ? EdmPrimitiveTypeKind.Decimal
: EdmPrimitiveTypeKind.String;
}
}