diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/OpenTypeDeserializerUtils.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/OpenTypeDeserializerUtils.java new file mode 100644 index 0000000000..eddf6a9ee9 --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/OpenTypeDeserializerUtils.java @@ -0,0 +1,131 @@ +/* + * 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; + +import org.apache.olingo.commons.api.edm.EdmAnnotation; +import org.apache.olingo.commons.api.edm.EdmMapping; +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmTerm; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.geo.SRID; +import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; + +import java.util.List; + +public class OpenTypeDeserializerUtils { + public static EdmProperty generateDynamicEdmProperty(final String propertyName, final EdmType edmType, + final boolean isCollection) { + return new EdmProperty() { + + @Override + public EdmType getType() { + return edmType; + } + + @Override + public boolean isCollection() { + return isCollection; + } + + @Override + public String getName() { + return propertyName; + } + + @Override + public EdmMapping getMapping() { + return null; + } + + @Override + public EdmAnnotation getAnnotation(EdmTerm term, String qualifier) { + return null; + } + + @Override + public List getAnnotations() { + return null; + } + + @Override + public String getMimeType() { + return null; + } + + @Override + public boolean isPrimitive() { + return edmType instanceof EdmPrimitiveType; + } + + @Override + public boolean isNullable() { + return false; + } + + @Override + public Integer getMaxLength() { + return null; + } + + @Override + public Integer getPrecision() { + if (EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.DateTimeOffset).equals(edmType)) { + return 3; + } + return null; + } + + @Override + public Integer getScale() { + return null; + } + + @Override + public String getScaleAsString() { + return null; + } + + @Override + public SRID getSrid() { + return null; + } + + @Override + public boolean isUnicode() { + return true; + } + + @Override + public String getDefaultValue() { + return null; + } + + @Override + public EdmType getTypeWithAnnotations() { + return getType(); + } + }; + } + + private OpenTypeDeserializerUtils() { + } +} diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java index 08f92639dd..bcc19b5109 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java @@ -77,6 +77,7 @@ import org.apache.olingo.commons.api.edm.geo.Polygon; import org.apache.olingo.commons.api.edm.geo.SRID; import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.deserializer.DeserializerException; import org.apache.olingo.server.api.deserializer.DeserializerException.MessageKeys; @@ -239,6 +240,10 @@ private Entity consumeEntityNode(final EdmEntityType edmEntityType, final Object // consume remaining json node fields consumeRemainingJsonNodeFields(edmEntityType, tree, entity); + if (edmEntityType.isOpenType()) { + consumeOpenEntityProperties(tree, entity); + } + assertJsonNodeIsEmpty(tree); return entity; @@ -563,6 +568,202 @@ private void consumeEntityProperties(final EdmEntityType edmEntityType, final Ob } } + private void consumeOpenEntityProperties(final ObjectNode tree, final Entity entity) throws DeserializerException { + List toRemove = new ArrayList(); + Iterator fieldNames = tree.fieldNames(); + while (fieldNames.hasNext()) { + String propertyName = fieldNames.next(); + JsonNode childNode = tree.get(propertyName); + if (entity.getProperty(propertyName) == null) { + entity.addProperty(readDynamicProperty(propertyName, childNode)); + toRemove.add(propertyName); + } else { + throw new DeserializerException("Duplicated property: " + propertyName, MessageKeys.DUPLICATE_PROPERTY); + } + } + tree.remove(toRemove); + } + + private Property readDynamicProperty(String propertyName, JsonNode node) throws DeserializerException { + Property property = null; + if (node == null || node.isNull()) { + property = new Property(null, propertyName, ValueType.PRIMITIVE, null); + } else if (node.isValueNode()) { + property = readDynamicPrimitiveProperty(propertyName, node); + } else if (node.isArray()) { + property = readDynamicCollectionProperty(propertyName, node); + } else if (node.isObject()) { + EdmType edmType = getDynamicEdmType(node); + switch (edmType.getKind()) { + case PRIMITIVE: + try { + Geospatial geospatial = readPrimitiveGeoValue(propertyName, (EdmPrimitiveType) edmType, (ObjectNode) node); + property = new Property(edmType.getFullQualifiedName().getFullQualifiedNameAsString(), propertyName, + ValueType.GEOSPATIAL, geospatial); + } catch (EdmPrimitiveTypeException e) { + throw new DeserializerException( + "Failed to load GeoJson element: " + propertyName, MessageKeys.UNKNOWN_CONTENT); + } + break; + case COMPLEX: + ComplexValue value = readDynamicComplexValue(propertyName, node); + property = new Property(value.getTypeName(), propertyName, ValueType.COMPLEX, value); + break; + default: + throw new DeserializerException( + "Unsupported dynamic property kind: " + edmType.getKind().name(), MessageKeys.UNSUPPORTED_FORMAT); + } + } else { + throw new DeserializerException("Unknown content: " + propertyName, MessageKeys.UNKNOWN_CONTENT); + } + return property; + } + + private Property readDynamicPrimitiveProperty(String propertyName, JsonNode node) throws DeserializerException { + // Cannot support all primitive type due to a Json ambiguity and limitation + String type; + Object value; + if (node.isBoolean()) { + type = EdmPrimitiveTypeKind.Boolean.getFullQualifiedName().getFullQualifiedNameAsString(); + value = node.asBoolean(); + } else if (node.isNumber()) { + JsonParser.NumberType numberType = node.numberType(); + switch (numberType) { + case INT: + type = EdmPrimitiveTypeKind.Int32.getFullQualifiedName().getFullQualifiedNameAsString(); + value = node.asInt(); + break; + case LONG: + type = EdmPrimitiveTypeKind.Int64.getFullQualifiedName().getFullQualifiedNameAsString(); + value = node.asLong(); + break; + case FLOAT: + case DOUBLE: + type = EdmPrimitiveTypeKind.Double.getFullQualifiedName().getFullQualifiedNameAsString(); + value = node.asDouble(); + break; + default: + throw new DeserializerException( + "Unsupported Json dynamic primitive type: " + numberType, MessageKeys.UNSUPPORTED_FORMAT); + } + } else if (node.isTextual()) { + type = EdmPrimitiveTypeKind.String.getFullQualifiedName().getFullQualifiedNameAsString(); + value = node.asText(); + } else { + throw new DeserializerException( + "Unsupported json dynamic primitive: " + propertyName, MessageKeys.UNSUPPORTED_FORMAT); + } + return new Property(type, propertyName, ValueType.PRIMITIVE, value); + } + + private ComplexValue readDynamicComplexValue(String propertyName, JsonNode node) throws DeserializerException { + return readDynamicComplexValue(propertyName, null, node); + } + + private ComplexValue readDynamicComplexValue(final String propertyName, final EdmComplexType type, + final JsonNode node) throws DeserializerException { + EdmComplexType edmComplexType = type; + if (edmComplexType == null) { + JsonNode typeField = node.get(Constants.JSON_TYPE); + if (typeField == null) { + throw new DeserializerException("Missing type of property: " + propertyName, MessageKeys.UNKNOWN_CONTENT); + } + String typeName = typeField.asText().substring(1); + try { + edmComplexType = serviceMetadata.getEdm().getComplexType(new FullQualifiedName(typeName)); + } catch (IllegalArgumentException e) { + throw new DeserializerException(e.getMessage(), MessageKeys.UNKNOWN_CONTENT); + } + if (edmComplexType == null) { + throw new DeserializerException("Unknown type: " + typeName, MessageKeys.UNKNOWN_CONTENT); + } + } + ComplexValue complexValue = new ComplexValue(); + complexValue.setTypeName(edmComplexType.getFullQualifiedName().getFullQualifiedNameAsString()); + + Iterator propertyNames = node.fieldNames(); + while (propertyNames.hasNext()) { + String name = propertyNames.next(); + if (!name.startsWith(ODATA_CONTROL_INFORMATION_PREFIX)) { + if (edmComplexType.getPropertyNames().contains(name)) { + EdmProperty propertyType = edmComplexType.getStructuralProperty(name); + complexValue.getValue().add(consumePropertyNode(name, propertyType.getType(), propertyType.isCollection(), + propertyType.isNullable(), propertyType.getMaxLength(), propertyType.getPrecision(), + propertyType.getScale(), propertyType.isUnicode(), propertyType.getMapping(), node.get(name))); + } else { + complexValue.getValue().add(readDynamicProperty(name, node.get(name))); + } + } + } + return complexValue; + } + + private Property readDynamicCollectionProperty(String propertyName, JsonNode node) throws DeserializerException { + List foundProperties = dynamicCollectionAsPropertyList(propertyName, node); + EdmType type = getTypeOfDynamicCollectionElements(foundProperties); + List values = new ArrayList(); + for (Property property : foundProperties) { + values.add(property.getValue()); + } + ValueType valueType; + switch (foundProperties.get(0).getValueType()) { + case PRIMITIVE: + valueType = ValueType.COLLECTION_PRIMITIVE; + break; + case GEOSPATIAL: + valueType = ValueType.COLLECTION_GEOSPATIAL; + break; + case COMPLEX: + valueType = ValueType.COLLECTION_COMPLEX; + break; + default: + throw new DeserializerException("Unsupported dynamic value type: " + foundProperties.get(0).getValueType(), + MessageKeys.UNSUPPORTED_FORMAT); + } + return new Property(type.getFullQualifiedName().getFullQualifiedNameAsString(), propertyName, valueType, values); + } + + private List dynamicCollectionAsPropertyList(String propertyName, JsonNode node) + throws DeserializerException{ + List properties = new ArrayList(); + Iterator it = node.iterator(); + while (it.hasNext()) { + JsonNode childNode = it.next(); + if (childNode.isArray()) { + throw new DeserializerException( + "Unsupported dynamic property: " + propertyName, MessageKeys.UNSUPPORTED_FORMAT); + } else if (childNode.isValueNode()) { + Property property = readDynamicPrimitiveProperty(propertyName, childNode); + properties.add(property); + } else if (childNode.isObject()) { + EdmType type = getDynamicEdmType(childNode); + switch (type.getKind()) { + case PRIMITIVE: + try { + Geospatial geospatial = readPrimitiveGeoValue( + propertyName, (EdmPrimitiveType) type, (ObjectNode) childNode); + properties.add(new Property(type.getFullQualifiedName().getFullQualifiedNameAsString(), + propertyName, ValueType.GEOSPATIAL, geospatial)); + } catch (EdmPrimitiveTypeException e) { + throw new DeserializerException( + "Failed to load a GeoJson element into array: " + propertyName, MessageKeys.UNKNOWN_CONTENT); + } + break; + case COMPLEX: + ComplexValue complex = readDynamicComplexValue(propertyName, (EdmComplexType) type, childNode); + properties.add(new Property(complex.getTypeName(), propertyName, ValueType.COMPLEX, complex)); + break; + default: + throw new DeserializerException( + "Unsupported dynamic property kind: " + type.getKind().name(), MessageKeys.UNSUPPORTED_FORMAT); + } + } else { + throw new DeserializerException("Unknown content: " + propertyName, MessageKeys.UNKNOWN_CONTENT); + } + } + return properties; + } + private void consumeExpandedNavigationProperties(final EdmEntityType edmEntityType, final ObjectNode node, final Entity entity, final ExpandTreeBuilder expandBuilder) throws DeserializerException { List navigationPropertyNames = edmEntityType.getNavigationPropertyNames(); @@ -712,8 +913,11 @@ private void consumePropertySingleNode(final String name, final EdmType type, jsonNode); property.setType(derivedType.getFullQualifiedName() .getFullQualifiedNameAsString()); - - value = readComplexNode(name, derivedType, isNullable, jsonNode); + if (((EdmStructuredType) derivedType).isOpenType()) { + value = readDynamicComplexValue(name, (EdmComplexType) derivedType, jsonNode); + } else { + value = readComplexNode(name, derivedType, isNullable, jsonNode); + } property.setValue(ValueType.COMPLEX, value); break; default: @@ -861,21 +1065,23 @@ private boolean isValidNull(final String name, final boolean isNullable, final J */ private Geospatial readPrimitiveGeoValue(final String name, final EdmPrimitiveType type, ObjectNode jsonNode) throws DeserializerException, EdmPrimitiveTypeException { - JsonNode typeNode = jsonNode.remove(Constants.ATTR_TYPE); + // protective copy for dynamic exploration + ObjectNode copyNode = jsonNode.deepCopy(); + JsonNode typeNode = copyNode.remove(Constants.ATTR_TYPE); if (typeNode != null && typeNode.isTextual()) { final Class geoDataType = jsonNameToGeoDataType.get(typeNode.asText()); if (geoDataType != null && (type == null || geoDataType.equals(type.getDefaultType()))) { - final JsonNode topNode = jsonNode.remove( + final JsonNode topNode = copyNode.remove( geoDataType.equals(GeospatialCollection.class) ? Constants.JSON_GEOMETRIES : Constants.JSON_COORDINATES); SRID srid = null; - if (jsonNode.has(Constants.JSON_CRS)) { + if (copyNode.has(Constants.JSON_CRS)) { srid = SRID.valueOf( - jsonNode.remove(Constants.JSON_CRS).get(Constants.PROPERTIES). + copyNode.remove(Constants.JSON_CRS).get(Constants.PROPERTIES). get(Constants.JSON_NAME).asText().split(":")[1]); } - assertJsonNodeIsEmpty(jsonNode); + assertJsonNodeIsEmpty(copyNode); if (topNode != null && topNode.isArray()) { final Geospatial.Dimension dimension = type == null || type.getName().startsWith("Geometry") ? @@ -1222,4 +1428,231 @@ private boolean isAssignable(final EdmStructuredType edmStructuredType, && (edmStructuredType.getFullQualifiedName().equals(edmStructuredTypeToAssign.getFullQualifiedName()) || isAssignable(edmStructuredType, edmStructuredTypeToAssign.getBaseType())); } + + private EdmType getDynamicEdmType(final JsonNode node) throws DeserializerException { + JsonNode typeField = node.get(Constants.JSON_TYPE); + if (typeField == null) { + if (node.isValueNode()) { + if (node.isBoolean()) { + return EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean); + } else if (node.isInt()) { + return EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int32); + } else if (node.isLong()) { + return EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int64); + } else if (node.isFloat() || typeField.isDouble()) { + return EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Double); + } else if (node.isTextual()) { + return EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.String); + } + } else if (node.isObject()) { + return retrieveDynamicGeoType(node); + } + throw new DeserializerException( + "Unknown content of Json node: " + node.toString(), MessageKeys.UNKNOWN_CONTENT); + } else { + String typeName = typeField.asText().substring(1); // remove first character: # + FullQualifiedName fnq = new FullQualifiedName(typeName); + EdmType edmType = serviceMetadata.getEdm().getComplexType(fnq); + if (edmType == null) { + edmType = serviceMetadata.getEdm().getEntityType(fnq); + } + if (edmType == null) { + edmType = serviceMetadata.getEdm().getEnumType(fnq); + } + if (edmType == null) { + edmType = serviceMetadata.getEdm().getTypeDefinition(fnq); + } + if (edmType == null) { + throw new DeserializerException("Unknown type: " + typeName, MessageKeys.UNKNOWN_CONTENT); + } + return edmType; + } + } + + private EdmType getTypeOfDynamicCollectionElements(final List properties) throws DeserializerException { + if (properties.isEmpty()) { + throw new DeserializerException("Unsupported empty dynamic collection", MessageKeys.UNSUPPORTED_FORMAT); + } + + EdmType edmType = null; + for (Property property : properties) { + EdmType foundEdmType; + String type = property.getType(); + if (type.startsWith("Edm")) { + foundEdmType = EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.valueOfFQN(type)); + } else { + foundEdmType = serviceMetadata.getEdm().getComplexType(new FullQualifiedName(type)); + } + + if (edmType == null) { + edmType = foundEdmType; + } else { + if (edmType instanceof EdmStructuredType) { + edmType = getBestBaseType((EdmStructuredType) edmType, (EdmStructuredType) foundEdmType); + if (edmType == null) { + throw new DeserializerException( + "Incompatible type into collection", MessageKeys.INVALID_VALUE_FOR_PROPERTY); + } + } else if (!edmType.equals(foundEdmType)) { + throw new DeserializerException("Incompatible type into collection", MessageKeys.INVALID_VALUE_FOR_PROPERTY); + } + } + } + return edmType; + } + + /** + * Retrieves the best base type between two given structural types. + * @param type first structural type + * @param otherType second structural type + * @return the best common base type, otherwise null. + */ + private EdmStructuredType getBestBaseType(final EdmStructuredType type, final EdmStructuredType otherType) { + if (type == null || otherType == null) { + return null; + } + + if (type.equals(otherType)) { + return type; + } + + if (type.equals(otherType.getBaseType())) { + return type; + } + + if (otherType.equals(type.getBaseType())) { + return otherType; + } + + List baseTypeList = getBaseTypeListOf(type); + List baseOtherTypeList = getBaseTypeListOf(otherType); + for (int i = 0; i < baseTypeList.size(); i++) { + for (int j = 0; j < baseOtherTypeList.size(); j++) { + if (baseTypeList.get(i).equals(baseOtherTypeList.get(j))) { + baseTypeList.get(i); + } + } + } + return null; + } + + /** + * Returns an ordered base type list of the given type. + * @param edmStructuredType type source + * @return an ordered list of EdmStructuredType. + */ + private List getBaseTypeListOf(final EdmStructuredType edmStructuredType) { + ArrayList baseTypeList = new ArrayList(); + EdmStructuredType baseType = edmStructuredType.getBaseType(); + while (baseType != null) { + baseTypeList.add(baseType); + baseType = baseType.getBaseType(); + } + return baseTypeList; + } + + private EdmType retrieveDynamicGeoType(final JsonNode node) throws DeserializerException { + JsonNode type = node.get(Constants.ATTR_TYPE); + if (type == null || !type.isTextual()) { + throw new DeserializerException("Geo-type" + node.toString(), MessageKeys.UNKNOWN_CONTENT); + } + String geoType = type.asText(); + JsonNode position; + if ("GeometryCollection".equals(geoType)) { + checkGeoJsonHasNamedChild(node, Constants.JSON_GEOMETRIES); + JsonNode geometriesNode = node.get(Constants.JSON_GEOMETRIES); + if (geometriesNode.size() == 0) { + return EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.GeometryCollection); + } + Property p = readDynamicProperty(Constants.JSON_GEOMETRIES, geometriesNode.get(0)); + if (p.getType().startsWith("Edm.Geometry")) { + return EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.GeometryCollection); + } else if (p.getType().startsWith("Edm.Geography")) { + return EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.GeographyCollection); + } + throw new DeserializerException("Not implemented yet", MessageKeys.NOT_IMPLEMENTED); + } else if ("Feature".equals(geoType)) { + throw new DeserializerException("GeoJson type Feature is not supported", MessageKeys.UNSUPPORTED_FORMAT); + } else { + checkGeoJsonHasNamedChild(node, Constants.JSON_COORDINATES); + position = retrieveGeoJsonPosition(geoType, node.get(Constants.JSON_COORDINATES)); + } + + if (position == null) { + throw new DeserializerException("Unknown GeoJson content", MessageKeys.UNKNOWN_CONTENT); + } + Geospatial.Dimension dimension = retrieveDimension(position); + String prefix = dimension.name().substring(0, 1) + dimension.name().substring(1).toLowerCase(); + return EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.valueOf(prefix + geoType)); + } + + /** + * Checks existence of a specific child into a GeoJson node. + * @param node GeoJson node + * @param childName child node name + * @throws DeserializerException if child is not found. + */ + private void checkGeoJsonHasNamedChild(final JsonNode node, final String childName) throws DeserializerException { + if (!node.has(childName)) { + throw new DeserializerException("Missing GeoJson property: " + childName, MessageKeys.UNKNOWN_CONTENT); + } + } + + /** + * Retrieves first Position of a GeoJson object containing coordinates. + * @param geoJsonType GeoJson type + * @param node coordinates Json node + * @return Json node representing the found position, otherwise null + */ + private JsonNode retrieveGeoJsonPosition(final String geoJsonType, final JsonNode node) { + if (node == null || !node.isArray()) { + return null; + } + JsonNode position = node; + if ("Point".equals(geoJsonType)) { + return position; + } + + position = position.get(0); + if (position != null) { + if (position.isArray() && position.size() > 0 + && ("LineString".equals(geoJsonType) || "MultiPoint".equals(geoJsonType))) { + return position; + } + position = position.get(0); + } + + if (position != null) { + if (position.isArray() && position.size() > 0 + && ("MultiLineString".equals(geoJsonType) || "Polygon".equals(geoJsonType))) { + return position; + } + position = position.get(0); + } + + if (position != null && position.isArray() && ("MultiPolygon".equals(geoJsonType))) { + return position; + } + return null; + } + + /** + * Retrieves dimension of a GeoJson geometry object Position. + * @param node GeoJson Position + * @return dimension of the GeoJson position + * @throws DeserializerException if the given node is not a GeoJson Position + */ + private Geospatial.Dimension retrieveDimension(final JsonNode node) throws DeserializerException { + if (!node.isArray()) { + throw new DeserializerException("GeoJson Position must be a array", MessageKeys.UNKNOWN_CONTENT); + } + switch (node.size()) { + case 2: + return Geospatial.Dimension.GEOMETRY; + case 3: + return Geospatial.Dimension.GEOGRAPHY; + default: + throw new DeserializerException("Invalid GeoJson Position", MessageKeys.INVALID_VALUE_FOR_PROPERTY); + } + } } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/xml/ODataXmlDeserializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/xml/ODataXmlDeserializer.java index 3854875119..32cccde502 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/xml/ODataXmlDeserializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/xml/ODataXmlDeserializer.java @@ -43,6 +43,7 @@ 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.Edm; import org.apache.olingo.commons.api.edm.EdmAction; import org.apache.olingo.commons.api.edm.EdmComplexType; import org.apache.olingo.commons.api.edm.EdmEntityType; @@ -51,6 +52,7 @@ import org.apache.olingo.commons.api.edm.EdmParameter; 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.EdmStructuredType; import org.apache.olingo.commons.api.edm.EdmType; @@ -58,12 +60,14 @@ import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.core.edm.EdmTypeInfo; import org.apache.olingo.commons.core.edm.primitivetype.AbstractGeospatialType; +import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.deserializer.DeserializerException; import org.apache.olingo.server.api.deserializer.DeserializerException.MessageKeys; import org.apache.olingo.server.api.deserializer.DeserializerResult; import org.apache.olingo.server.api.deserializer.ODataDeserializer; import org.apache.olingo.server.core.deserializer.DeserializerResultImpl; +import org.apache.olingo.server.core.deserializer.OpenTypeDeserializerUtils; public class ODataXmlDeserializer implements ODataDeserializer { @@ -136,28 +140,32 @@ private Object primitive(final XMLEventReader reader, final StartElement start, private Object complex(final XMLEventReader reader, final StartElement start, final EdmComplexType edmComplex) throws XMLStreamException, EdmPrimitiveTypeException, DeserializerException { ComplexValue value = new ComplexValue(); - EdmType resolvedType = edmComplex; + // retrieve complex edm type + EdmComplexType resolvedType = (EdmComplexType) getDerivedType(edmComplex, extractTypeString(start)); + // load complex properties boolean foundEndProperty = false; while (reader.hasNext() && !foundEndProperty) { final XMLEvent event = reader.nextEvent(); - - if (event.isStartElement()) { - //Get the derived type from the element tag - final Attribute attrType = start.getAttributeByName(typeQName); - if (attrType != null ) { - String type = new EdmTypeInfo.Builder().setTypeExpression(attrType.getValue()).build().internal(); - if (type.startsWith("Collection(") && type.endsWith(")")) { - type = type.substring(11, type.length()-1); + if (event.isStartElement()) { + StartElement se = event.asStartElement(); + EdmProperty p = (EdmProperty) resolvedType.getProperty(se.getName().getLocalPart()); + if (p == null) { + if (resolvedType.isOpenType()) { + String typeString = extractTypeString(se); + boolean isCollection = extractTypeIsCollection(se); + EdmType type = getEdmType(typeString); + p = OpenTypeDeserializerUtils.generateDynamicEdmProperty(se.getName().getLocalPart(), type, isCollection); + } else { + throw new DeserializerException("An unknown property found into complex", MessageKeys.UNKNOWN_CONTENT); } - resolvedType = getDerivedType(edmComplex, type); } - - - StartElement se = event.asStartElement(); - EdmProperty p = (EdmProperty) ((EdmComplexType)resolvedType).getProperty(se.getName().getLocalPart()); - value.getValue().add(property(reader, se, p.getType(), p.isNullable(), p.getMaxLength(), - p.getPrecision(), p.getScale(), p.isUnicode(), p.isCollection())); - value.setTypeName(resolvedType.getFullQualifiedName().getFullQualifiedNameAsString()); + if (!complexValueContainsPropertyNamed(value, se.getName().getLocalPart())) { + value.getValue().add(property(reader, se, p.getType(), p.isNullable(), p.getMaxLength(), + p.getPrecision(), p.getScale(), p.isUnicode(), p.isCollection())); + value.setTypeName(p.getType().getFullQualifiedName().getFullQualifiedNameAsString()); + } else { + throw new DeserializerException("Duplicated property into complex", MessageKeys.DUPLICATE_PROPERTY); + } } if (event.isEndElement() && start.getName().equals(event.asEndElement().getName())) { foundEndProperty = true; @@ -166,6 +174,38 @@ private Object complex(final XMLEventReader reader, final StartElement start, fi return value; } + private String extractTypeString(StartElement xmlStartElement) { + Attribute typeAttribute = xmlStartElement.getAttributeByName(typeQName); + if (typeAttribute != null) { + String typeString = typeAttribute.getValue(); + if (typeString.startsWith("#Collection(") && typeString.endsWith(")")) { + typeString = typeString.substring(11, typeString.length() - 1); + } + return typeString.startsWith("#") ? typeString.substring(1) : typeString; + } + return null; + } + + private boolean extractTypeIsCollection(StartElement xmlStartElement) { + Attribute typeAttribute = xmlStartElement.getAttributeByName(typeQName); + if (typeAttribute != null) { + String typeString = typeAttribute.getValue(); + if (typeString.startsWith("#Collection(") && typeString.endsWith(")")) { + return true; + } + } + return false; + } + + private boolean complexValueContainsPropertyNamed(ComplexValue value, String propertyName) { + for (final Property property : value.getValue()) { + if (property.getName().equals(propertyName)) { + return true; + } + } + return false; + } + private void collection(final Valuable valuable, final XMLEventReader reader, final StartElement start, final EdmType edmType, final boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, final boolean isUnicode) throws XMLStreamException, EdmPrimitiveTypeException, @@ -436,20 +476,30 @@ private void properties(final XMLEventReader reader, final StartElement start, f final XMLEvent event = reader.nextEvent(); if (event.isStartElement()) { + Property property; String propertyName = event.asStartElement().getName().getLocalPart(); EdmProperty edmProperty = (EdmProperty) edmEntityType.getProperty(propertyName); if (edmProperty == null) { - throw new DeserializerException("Invalid Property in payload with name: " + propertyName, - DeserializerException.MessageKeys.UNKNOWN_CONTENT, propertyName); + if (edmEntityType.isOpenType()) { + if (containsPropertyName(entity, propertyName)) { + throw new DeserializerException("Duplicated property: " + propertyName, MessageKeys.DUPLICATE_PROPERTY); + } + property = dynamicProperty(reader, event.asStartElement()); + } else { + throw new DeserializerException("Invalid Property in payload with name: " + propertyName, + DeserializerException.MessageKeys.UNKNOWN_CONTENT, propertyName); + } + } else { + property = property(reader, event.asStartElement(), + edmProperty.getType(), + edmProperty.isNullable(), + edmProperty.getMaxLength(), + edmProperty.getPrecision(), + edmProperty.getScale(), + edmProperty.isUnicode(), + edmProperty.isCollection()); } - entity.getProperties().add(property(reader, event.asStartElement(), - edmProperty.getType(), - edmProperty.isNullable(), - edmProperty.getMaxLength(), - edmProperty.getPrecision(), - edmProperty.getScale(), - edmProperty.isUnicode(), - edmProperty.isCollection())); + entity.getProperties().add(property); } if (event.isEndElement() && start.getName().equals(event.asEndElement().getName())) { @@ -458,6 +508,71 @@ private void properties(final XMLEventReader reader, final StartElement start, f } } + private boolean containsPropertyName(Entity entity, String propertyName) { + for (final Property property : entity.getProperties()) { + if (propertyName.equals(property.getName())) { + return true; + } + } + return false; + } + + private Property dynamicProperty(XMLEventReader reader, StartElement start) throws XMLStreamException, + DeserializerException, EdmPrimitiveTypeException { + EdmType edmType; + boolean isCollection = false; + + Attribute attributeType = start.getAttributeByName(typeQName); + String typeName = null; + if (attributeType != null) { + typeName = attributeType.getValue(); + if (typeName.startsWith("#Collection(")) { + isCollection = true; + typeName = typeName.substring(12, (typeName.length() - 1)); + } + } + edmType = getEdmType(typeName); + return property(reader, start, edmType, true, null, null, null, true, isCollection); + } + + private EdmType getEdmType(final String xmlTypeString) throws DeserializerException { + if (xmlTypeString == null) { + return EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.String); + } + + String typeName = xmlTypeString.startsWith("#") ? xmlTypeString.substring(1) : xmlTypeString; + EdmType edmType; + try { + // retrieve if primitive + edmType = EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.valueOf(typeName)); + } catch (IllegalArgumentException e) { + // retrieve into the metadata service + edmType = findTypeIntoMetadataService(typeName); + } + return edmType; + } + + private EdmType findTypeIntoMetadataService(final String typeName) throws DeserializerException { + EdmType type; + FullQualifiedName fnq = new FullQualifiedName(typeName); + Edm edm = serviceMetadata.getEdm(); + + type = edm.getTypeDefinition(fnq); + if (type == null) { + type = edm.getEnumType(fnq); + } + if (type == null) { + type = edm.getComplexType(fnq); + } + if (type == null) { + type = edm.getEntityType(fnq); + } + if (type == null) { + throw new DeserializerException("Unknown type: " + typeName, MessageKeys.UNKNOWN_CONTENT); + } + return type; + } + private Entity entityRef(final StartElement start) throws XMLStreamException { final Entity entity = new Entity(); diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/OpenTypeSerializerUtils.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/OpenTypeSerializerUtils.java new file mode 100644 index 0000000000..344556eeca --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/OpenTypeSerializerUtils.java @@ -0,0 +1,140 @@ +/* + * 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; + +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.data.ValueType; +import org.apache.olingo.commons.api.edm.EdmAnnotation; +import org.apache.olingo.commons.api.edm.EdmMapping; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmTerm; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.geo.SRID; +import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; +import org.apache.olingo.server.api.ServiceMetadata; + +import java.util.Collections; +import java.util.List; + +public class OpenTypeSerializerUtils { + public static EdmProperty generateDynamicEdmProperty(final ServiceMetadata metadata, final Property property) { + return new EdmProperty() { + @Override + public String getMimeType() { + return null; + } + + @Override + public boolean isPrimitive() { + return property.isPrimitive(); + } + + @Override + public boolean isNullable() { + return true; + } + + @Override + public Integer getMaxLength() { + return null; + } + + @Override + public Integer getPrecision() { + return null; + } + + @Override + public Integer getScale() { + return null; + } + + @Override + public String getScaleAsString() { + return null; + } + + @Override + public SRID getSrid() { + return null; + } + + @Override + public boolean isUnicode() { + return true; + } + + @Override + public String getDefaultValue() { + return null; + } + + @Override + public EdmType getTypeWithAnnotations() { + return getType(); + } + + @Override + public EdmAnnotation getAnnotation(EdmTerm term, String qualifier) { + return null; + } + + @Override + public List getAnnotations() { + return Collections.emptyList(); + } + + @Override + public EdmMapping getMapping() { + return null; + } + + @Override + public String getName() { + return property.getName(); + } + + @Override + public EdmType getType() { + ValueType valueType = property.getValueType(); + if (ValueType.PRIMITIVE.equals(valueType) || ValueType.COLLECTION_PRIMITIVE.equals(valueType) + || ValueType.GEOSPATIAL.equals(valueType) || ValueType.COLLECTION_GEOSPATIAL.equals(valueType)) { + return EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.valueOfFQN(property.getType())); + } else if (ValueType.ENUM.equals(valueType) || ValueType.COLLECTION_ENUM.equals(valueType)) { + return metadata.getEdm().getEnumType(new FullQualifiedName(property.getType())); + } else if (ValueType.COMPLEX.equals(valueType) || ValueType.COLLECTION_COMPLEX.equals(valueType)) { + return metadata.getEdm().getComplexType(new FullQualifiedName(property.getType())); + } else if (ValueType.ENTITY.equals(valueType) || ValueType.COLLECTION_ENTITY.equals(valueType)) { + return metadata.getEdm().getEntityType(new FullQualifiedName(property.getType())); + } + return null; + } + + @Override + public boolean isCollection() { + return property.isCollection(); + } + }; + } + + private OpenTypeSerializerUtils() { + } +} diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java index aed0bb8810..23e8e5fd7f 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java @@ -21,9 +21,11 @@ import java.io.IOException; import java.io.OutputStream; import java.net.URI; +import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -86,6 +88,7 @@ import org.apache.olingo.server.api.uri.queryoption.SelectOption; import org.apache.olingo.server.core.ODataWritableContent; import org.apache.olingo.server.core.serializer.AbstractODataSerializer; +import org.apache.olingo.server.core.serializer.OpenTypeSerializerUtils; 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; @@ -517,6 +520,8 @@ protected void writeProperties(final ServiceMetadata metadata, final EdmStructur ExpandSelectHelper.getSelectedPropertyNames(select.getSelectItems()); addKeyPropertiesToSelected(selected, type); Set> expandedPaths = ExpandSelectHelper.getExpandedItemsPath(expand); + // write defined properties + List wroteProperies = new ArrayList(type.getPropertyNames().size()); for (final String propertyName : type.getPropertyNames()) { if (all || selected.contains(propertyName)) { final EdmProperty edmProperty = type.getStructuralProperty(propertyName); @@ -524,8 +529,21 @@ protected void writeProperties(final ServiceMetadata metadata, final EdmStructur final Set> selectedPaths = all || edmProperty.isPrimitive() ? null : ExpandSelectHelper.getSelectedPaths(select.getSelectItems(), propertyName); writeProperty(metadata, edmProperty, property, selectedPaths, json, expandedPaths, linked, expand); + wroteProperies.add(property); } } + // write dynamic properties + if (type.isOpenType()) { + List dynamicProperties = new ArrayList(properties); + dynamicProperties.removeAll(wroteProperies); + for (final Property property : dynamicProperties) { + final EdmProperty edmProperty = OpenTypeSerializerUtils.generateDynamicEdmProperty(metadata, property); + final Set> selectedPaths = all || edmProperty.isPrimitive() ? null : + ExpandSelectHelper.getSelectedPaths(select.getSelectItems(), property.getName()); + writeProperty(metadata, edmProperty, property, selectedPaths, json, expandedPaths, linked, expand); + } + } + } private void addKeyPropertiesToSelected(Set selected, EdmStructuredType type) { @@ -1084,7 +1102,7 @@ protected void writeComplexValue(final ServiceMetadata metadata, } } } - + for (final String propertyName : type.getPropertyNames()) { final Property property = findProperty(propertyName, properties); if (selectedPaths == null || ExpandSelectHelper.isSelected(selectedPaths, propertyName)) { @@ -1098,6 +1116,23 @@ protected void writeComplexValue(final ServiceMetadata metadata, } catch (DecoderException e) { throw new SerializerException(IO_EXCEPTION_TEXT, e, SerializerException.MessageKeys.IO_EXCEPTION); } + if (type.isOpenType()) { + + Iterator openProperties = properties.stream() + .filter(prop -> type.getProperty(prop.getName()) == null) + .filter(prop -> type.getNavigationProperty(prop.getName()) == null) + .filter(prop -> selectedPaths == null || ExpandSelectHelper.isSelected(selectedPaths, prop.getName())) + .iterator(); + + for (; openProperties.hasNext(); ) { + Property property = openProperties.next(); + final EdmProperty edmProperty = OpenTypeSerializerUtils.generateDynamicEdmProperty(metadata, property); + writeProperty(metadata, edmProperty, property, + selectedPaths == null ? null + : ExpandSelectHelper.getReducedSelectedPaths(selectedPaths, property.getName()), + json, expandedPaths, linked, expand); + } + } } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializer.java index 8d6ac0b961..51a6c6353f 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializer.java @@ -22,6 +22,7 @@ import java.io.OutputStream; import java.net.URI; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -76,6 +77,7 @@ import org.apache.olingo.server.api.uri.queryoption.SelectOption; import org.apache.olingo.server.core.ODataWritableContent; import org.apache.olingo.server.core.serializer.AbstractODataSerializer; +import org.apache.olingo.server.core.serializer.OpenTypeSerializerUtils; 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.ContextURLBuilder; @@ -630,6 +632,8 @@ protected void writeProperties(final ServiceMetadata metadata, final EdmStructur ExpandSelectHelper.getSelectedPropertyNames(select.getSelectItems()); addKeyPropertiesToSelected(selected, type); Set> expandedPaths = ExpandSelectHelper.getExpandedItemsPath(expand); + // write defined properties + List wroteProperties = new ArrayList(type.getPropertyNames().size()); for (final String propertyName : type.getPropertyNames()) { if (all || selected.contains(propertyName)) { final EdmProperty edmProperty = type.getStructuralProperty(propertyName); @@ -638,6 +642,22 @@ protected void writeProperties(final ServiceMetadata metadata, final EdmStructur ExpandSelectHelper.getSelectedPaths(select.getSelectItems(), propertyName); writeProperty(metadata, edmProperty, property, selectedPaths, xml10InvalidCharReplacement, writer, expandedPaths, linked, expand); + wroteProperties.add(property); + } + } + // write dynamic properties + if (type.isOpenType()) { + List dynamicProperties = new ArrayList(properties); + dynamicProperties.removeAll(wroteProperties); + for (final Property property : dynamicProperties) { + final String propertyName = property.getName(); + if (all || selected.contains(propertyName)) { + final EdmProperty edmProperty = OpenTypeSerializerUtils.generateDynamicEdmProperty(metadata, property); + final Set> selectedPaths = all || edmProperty.isNullable() ? null : + ExpandSelectHelper.getSelectedPaths(select.getSelectItems(), propertyName); + writeProperty(metadata, edmProperty, property, selectedPaths, + xml10InvalidCharReplacement, writer, expandedPaths, linked, expand); + } } } } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java index 855d7ace61..f858343944 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java @@ -55,11 +55,7 @@ import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.serializer.SerializerException; import org.apache.olingo.server.api.uri.UriHelper; -import org.apache.olingo.server.tecsvc.provider.ActionProvider; -import org.apache.olingo.server.tecsvc.provider.ComplexTypeProvider; -import org.apache.olingo.server.tecsvc.provider.EntityTypeProvider; -import org.apache.olingo.server.tecsvc.provider.FunctionProvider; -import org.apache.olingo.server.tecsvc.provider.SchemaProvider; +import org.apache.olingo.server.tecsvc.provider.*; public class DataCreator { @@ -106,6 +102,7 @@ public DataCreator(final OData odata, final Edm edm) { data.put("ETBaseCont", createETBaseCont(edm, odata)); data.put("ETTwoCont", createETTwoCont(edm, odata)); data.put("ESStreamOnComplexProp", createETStreamOnComplexProp(edm, odata)); + data.put("OESTwoPrim", createOESTwoPrim(edm, odata)); linkSINav(data); linkESTwoPrim(data); @@ -2506,4 +2503,29 @@ private EntityCollection createETTwoCont(final Edm edm, final OData odata) { entityCollection.getEntities().get(2).setId(URI.create(id)); return entityCollection; } + + private EntityCollection createOESTwoPrim(final Edm edm, final OData odata) { + EntityCollection entityCollection = new EntityCollection(); + + entityCollection.getEntities().add(new Entity() + .addProperty(new Property(PropertyProvider.propertyId.getType(), PropertyProvider.propertyId.getName(), + ValueType.PRIMITIVE, 1)) + .addProperty(new Property(PropertyProvider.propertyString.getType(), PropertyProvider.propertyString.getName(), + ValueType.PRIMITIVE, "foo"))); + + entityCollection.getEntities().add(new Entity() + .addProperty(new Property(PropertyProvider.propertyId.getType(), PropertyProvider.propertyId.getName(), + ValueType.PRIMITIVE, 2)) + .addProperty(new Property(PropertyProvider.propertyString.getType(), PropertyProvider.propertyString.getName(), + ValueType.PRIMITIVE, "bar")) + .addProperty(new Property(PropertyProvider.propertyBoolean.getType(), PropertyProvider.propertyBoolean.getName(), + ValueType.PRIMITIVE, true)) + .addProperty(new Property(PropertyProvider.propertyInt16.getType(), PropertyProvider.propertyInt16.getName(), + ValueType.PRIMITIVE, Short.MAX_VALUE))); + + setEntityType(entityCollection, edm.getEntityType(EntityTypeProvider.nameOETTwoPrim)); + createEntityId(edm, odata, "OESTwoPrim", entityCollection); + createOperations("OESTwoPrim", entityCollection, EntityTypeProvider.nameOETTwoPrim); + return entityCollection; + } } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ActionProvider.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ActionProvider.java index f280a102ba..92a3485e64 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ActionProvider.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ActionProvider.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; import org.apache.olingo.commons.api.ex.ODataException; import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.edm.provider.CsdlAction; @@ -121,6 +122,7 @@ public class ActionProvider { new FullQualifiedName(SchemaProvider.NAMESPACE, "UARTTwoParam"); public static final FullQualifiedName nameUARTByteNineParam = new FullQualifiedName(SchemaProvider.NAMESPACE, "UARTByteNineParam"); + public static final FullQualifiedName nameUARTPrim = new FullQualifiedName(SchemaProvider.NAMESPACE, "UARTPrim"); public static List getBoundActionsForEntityType(FullQualifiedName entityType) throws ODataException { FullQualifiedName[] actionNames = {nameBAESAllPrimRTETAllPrim, @@ -483,6 +485,13 @@ public static List getActions(final FullQualifiedName actionName) th new CsdlParameter().setName("BindingParam").setType(EntityTypeProvider.nameETTwoKeyNav) .setNullable(false))) .setReturnType(new CsdlReturnType().setType(ComplexTypeProvider.nameCTTwoBasePrimCompNav))); + } else if (actionName.equals(nameUARTPrim)) { + return Collections.singletonList(new CsdlAction() + .setName(nameUARTPrim.getName()) + .setParameters(Collections.singletonList(new CsdlParameter() + .setName("ParameterOCTNoProp") + .setType(ComplexTypeProvider.nameOCTNoProp))) + .setReturnType(new CsdlReturnType().setType(EdmPrimitiveTypeKind.Boolean.getFullQualifiedName()))); } return null; } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ComplexTypeProvider.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ComplexTypeProvider.java index e0106c35d9..d096ddf1de 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ComplexTypeProvider.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ComplexTypeProvider.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import org.apache.olingo.commons.api.ex.ODataException; import org.apache.olingo.commons.api.edm.FullQualifiedName; @@ -61,6 +62,8 @@ public class ComplexTypeProvider { public static final FullQualifiedName nameCTWithStreamProp = new FullQualifiedName(SchemaProvider.NAMESPACE, "CTWithStreamProp"); + public static final FullQualifiedName nameOCTNoProp = new FullQualifiedName(SchemaProvider.NAMESPACE, "OCTNoProp"); + public CsdlComplexType getComplexType(final FullQualifiedName complexTypeName) throws ODataException { if (complexTypeName.equals(nameCTPrim)) { @@ -227,6 +230,11 @@ public CsdlComplexType getComplexType(final FullQualifiedName complexTypeName) t PropertyProvider.propertyComp_CTTwoPrim)) .setNavigationProperties(Arrays.asList(PropertyProvider.navPropertyETStreamOnComplexProp_ETStreamNav, PropertyProvider.navPropertyETStreamOnComplexPropMany_ETStreamNav)); + } else if (complexTypeName.equals(nameOCTNoProp)) { + return new CsdlComplexType() + .setName(nameOCTNoProp.getName()) + .setOpenType(true) + .setProperties(Collections.emptyList()); } return null; diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ContainerProvider.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ContainerProvider.java index 02ddb5bb87..0c2f657fc3 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ContainerProvider.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ContainerProvider.java @@ -112,7 +112,8 @@ public CsdlEntityContainer getEntityContainer() throws ODataException { entitySets.add(prov.getEntitySet(ContainerProvider.nameContainer, "ESPeople")); entitySets.add(prov.getEntitySet(ContainerProvider.nameContainer, "ESTwoPrimDerived")); entitySets.add(prov.getEntitySet(ContainerProvider.nameContainer, "ESAllPrimDerived")); - entitySets.add(prov.getEntitySet(ContainerProvider.nameContainer, "ESDelta")); + entitySets.add(prov.getEntitySet(ContainerProvider.nameContainer, "ESDelta")); + entitySets.add(prov.getEntitySet(ContainerProvider.nameContainer, "OESTwoPrim")); // Singletons List singletons = new ArrayList(); @@ -754,6 +755,10 @@ public CsdlEntitySet getEntitySet(final FullQualifiedName entityContainer, final .setPath(PropertyProvider.navPropertyKeyAsSegment.getName()) .setTarget("ESKeyAsSegmentString") )); + } else if (name.equals("OESTwoPrim")) { + return new CsdlEntitySet() + .setName("OESTwoPrim") + .setType(EntityTypeProvider.nameOETTwoPrim); } } return null; diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/EntityTypeProvider.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/EntityTypeProvider.java index 42f30bd754..2349ce01a1 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/EntityTypeProvider.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/EntityTypeProvider.java @@ -101,6 +101,9 @@ public class EntityTypeProvider { public static final FullQualifiedName nameETStreamOnComplexProp = new FullQualifiedName(SchemaProvider.NAMESPACE, "ETStreamOnComplexProp"); + public static final FullQualifiedName nameOETTwoPrim = + new FullQualifiedName(SchemaProvider.NAMESPACE, "OETTwoPrim"); + public static final FullQualifiedName nameETKeyAsSegmentString = new FullQualifiedName(SchemaProvider.NAMESPACE, "ETKeyAsSegmentString"); @@ -586,6 +589,12 @@ public CsdlEntityType getEntityType(final FullQualifiedName entityTypeName) thro PropertyProvider.propertyInt32, PropertyProvider.propertyEntityStream, PropertyProvider.propertyCompWithStream_CTWithStreamProp )); + } else if (entityTypeName.equals(nameOETTwoPrim)) { + return new CsdlEntityType() + .setName(nameOETTwoPrim.getName()) + .setKey(Arrays.asList(new CsdlPropertyRef().setName(PropertyProvider.propertyId.getName()))) + .setOpenType(true) + .setProperties(Arrays.asList(PropertyProvider.propertyId, PropertyProvider.propertyString)); } else if (entityTypeName.equals(nameETKeyAsSegmentString)) { return new CsdlEntityType() .setName("ETKeyAsSegmentString") diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/FunctionProvider.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/FunctionProvider.java index 182469d636..b90b622afc 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/FunctionProvider.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/FunctionProvider.java @@ -135,6 +135,9 @@ public class FunctionProvider { public static final FullQualifiedName nameBFCESTwoKeyNavRTCollDecimal = new FullQualifiedName(SchemaProvider.NAMESPACE, "BFCESTwoKeyNavRTCollDecimal"); + + public static final FullQualifiedName nameBFOETTwoPrimRTOCTNoProp = + new FullQualifiedName(SchemaProvider.NAMESPACE, "BFOETTwoPrimRTOCTNoProp"); // Unknown public static final FullQualifiedName name_FC_RTTimeOfDay_ = @@ -239,7 +242,8 @@ public static List getBoundFunctionsForType(FullQualifiedName enti nameBFCESKeyNavRTESTwoKeyNav, nameBFCESTwoKeyNavRTCollDecimal, nameBFNESTwoKeyNavRTString, - name_FC_RTTimeOfDay_ + name_FC_RTTimeOfDay_, + nameBFOETTwoPrimRTOCTNoProp }; List functions = new ArrayList(); @@ -1150,6 +1154,14 @@ public static List getFunctions(final FullQualifiedName functionNa .setReturnType( new CsdlReturnType().setType(EntityTypeProvider.nameETTwoKeyNav).setCollection(true) .setNullable(false))); + } else if (functionName.equals(nameBFOETTwoPrimRTOCTNoProp)) { + return Collections.singletonList( + new CsdlFunction() + .setName(nameBFOETTwoPrimRTOCTNoProp.getName()) + .setBound(true) + .setParameters(Collections.singletonList(new CsdlParameter() + .setName("BindingParam").setType(EntityTypeProvider.nameOETTwoPrim).setNullable(false))) + .setReturnType(new CsdlReturnType().setType(ComplexTypeProvider.nameOCTNoProp))); } return null; diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java index ba4462db53..24b312409d 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java @@ -237,6 +237,28 @@ public void wrongType() throws Exception { expectException("{\"ParameterInt16\":[42]}", "UARTParam", null, MessageKeys.INVALID_JSON_TYPE_FOR_PROPERTY); } + @Test + public void openComplex() throws Exception { + String payload = "{\"ParameterOCTNoProp\":{\"status\":\"PENDING\"}}"; + Map parameters = deserialize(payload, "UARTPrim", null); + assertNotNull(parameters); + + Parameter parameter = parameters.get("ParameterOCTNoProp"); + assertNotNull(parameter); + assertTrue(parameter.isComplex()); + assertFalse(parameter.isCollection()); + + ComplexValue complex = parameter.asComplex(); + assertEquals(1, complex.getValue().size()); + assertEquals("PENDING", complex.getValue().get(0).getValue()); + } + + @Test + public void openComplexTwice() { + String payload = "{\"ParameterOCTNoProp\":{\"status\":\"PENDING\",\"status\":\"RUNNING\"}}"; + expectException(payload, "UATRPrim", null, MessageKeys.DUPLICATE_PROPERTY); + } + private Parameter deserializeUARTByteNineParam(final String parameterName, final String parameterJsonValue) throws DeserializerException { final Map parameters = deserialize( diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java index a6f380ae20..deb6ee5166 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java @@ -55,6 +55,7 @@ import org.apache.olingo.commons.api.edm.geo.MultiPolygon; import org.apache.olingo.commons.api.edm.geo.Point; import org.apache.olingo.commons.api.edm.geo.Polygon; +import org.apache.olingo.commons.api.edm.geo.SRID; import org.apache.olingo.commons.api.edm.provider.CsdlMapping; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.server.api.OData; @@ -1831,6 +1832,161 @@ public void ieee754NotCompatibleAsString() throws Exception { DeserializerException.MessageKeys.INVALID_VALUE_FOR_PROPERTY); } + @Test + public void openEntity() throws Exception { + String payload = "{" + + "\"id\":5," + + "\"PropertyString\":\"foobar\"," + + "\"value\":null," + + "\"open\":true," + + "\"complex\":{\"@odata.type\":\"#olingo.odata.test1.CTPrim\",\"PropertyInt16\":512}," + + "\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[0.0,0.0],[5.0,0.0],[5.0,5.0],[0.0,5.0],[0.0,0.0]]]}," + + "\"geography\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0,1.0],\"crs\":{\"type\":\"name\"," + + "\"properties\":{\"name\":\"EPSG:2970\"}}}," + + "\"geo_collection\":{\"type\":\"GeometryCollection\",\"geometries\":[" + + "{\"type\":\"LineString\",\"coordinates\":[[5.0,3.0,1.0],[7.0,5.0,2.0]]}" +// + "{\"type\":\"MultiPoint\",\"coordinates\":[[0.0,0.0,0.0],[1.0,1.0,1.0]]}" + + "]}," + + "\"array_prim\":[\"a\", \"b\", \"c\"]," + + "\"array_complex\":[" + + "{\"@odata.type\":\"#olingo.odata.test1.CTBase\", \"PropertyInt16\":1,\"PropertyString\":\"x\"," + + "\"AdditionalPropString\":\"foo\"}," + + "{\"@odata.type\":\"#olingo.odata.test1.CTTwoPrim\", \"PropertyInt16\":2,\"PropertyString\":\"y\"}," + + "{\"@odata.type\":\"#olingo.odata.test1.CTTwoPrim\", \"PropertyInt16\":3,\"PropertyString\":\"z\"}" + + "]," + + "" + + "\"price\":" + Long.MAX_VALUE + + "}"; + Entity entity = deserialize(payload, "OETTwoPrim"); + Assert.assertNotNull(entity); + Assert.assertEquals(11, entity.getProperties().size()); + + // check defined properties + Assert.assertEquals(5, entity.getProperty("id").getValue()); + Assert.assertEquals("foobar", entity.getProperty("PropertyString").getValue()); + + // check basic property + Assert.assertTrue(entity.getProperty("value").isNull()); + Assert.assertTrue((Boolean) entity.getProperty("open").getValue()); + Assert.assertEquals(Long.MAX_VALUE, entity.getProperty("price").getValue()); + + // check complex property + Property property = entity.getProperty("complex"); + Assert.assertNotNull(property); + Assert.assertTrue(property.isComplex()); + Assert.assertFalse(property.isCollection()); + Assert.assertEquals(ValueType.COMPLEX, property.getValueType()); + Assert.assertEquals("olingo.odata.test1.CTPrim", property.getType()); + ComplexValue complex = (ComplexValue) property.getValue(); + Assert.assertNotNull(complex); + Assert.assertEquals("olingo.odata.test1.CTPrim", complex.getTypeName()); + Assert.assertEquals(1, complex.getValue().size()); + Property complexProperty = complex.getValue().get(0); + Assert.assertEquals("Edm.Int16", complexProperty.getType()); + Assert.assertEquals(ValueType.PRIMITIVE, complexProperty.getValueType()); + Assert.assertEquals((short) 512, complexProperty.getValue()); + + // check geo-spacial property + property = entity.getProperty("geometry"); + Assert.assertNotNull(property); + Assert.assertTrue(property.isGeospatial()); + Assert.assertFalse(property.isCollection()); + Assert.assertEquals(ValueType.GEOSPATIAL, property.getValueType()); + Assert.assertEquals("Edm.GeometryPolygon", property.getType()); + Geospatial geospatial = (Geospatial) property.getValue(); + Assert.assertEquals(Geospatial.Dimension.GEOMETRY, geospatial.getDimension()); + Assert.assertEquals(Geospatial.Type.POLYGON, geospatial.getGeoType()); +/* FIXME default SRID cause SRID#eqauls issue + * SRID srid = SRID.valueOf("0"); + * srid.setDimension(Geospatial.Dimension.GEOMETRY); + * Assert.assertEquals(srid, geospatial.getSrid()); + */ + Assert.assertEquals("0", geospatial.getSrid().toString()); + property = entity.getProperty("geography"); + Assert.assertNotNull(property); + Assert.assertTrue(property.isGeospatial()); + Assert.assertFalse(property.isCollection()); + Assert.assertEquals(ValueType.GEOSPATIAL, property.getValueType()); + Assert.assertEquals("Edm.GeographyPoint", property.getType()); + geospatial = (Geospatial) property.getValue(); + Assert.assertEquals(Geospatial.Dimension.GEOGRAPHY, geospatial.getDimension()); + Assert.assertEquals(Geospatial.Type.POINT, geospatial.getGeoType()); + SRID srid = SRID.valueOf("2970"); + srid.setDimension(Geospatial.Dimension.GEOGRAPHY); + Assert.assertEquals(srid, geospatial.getSrid()); + property = entity.getProperty("geo_collection"); + Assert.assertNotNull(property); + Assert.assertTrue(property.isGeospatial()); + Assert.assertFalse(property.isCollection()); + Assert.assertEquals(ValueType.GEOSPATIAL, property.getValueType()); + Assert.assertEquals("Edm.GeographyCollection", property.getType()); + Assert.assertNotNull(property.getValue()); + + // check basic array property + property = entity.getProperty("array_prim"); + Assert.assertNotNull(property); + Assert.assertTrue(property.isCollection()); + Assert.assertTrue(property.isPrimitive()); + Assert.assertEquals(ValueType.COLLECTION_PRIMITIVE, property.getValueType()); + Assert.assertEquals("Edm.String", property.getType()); + Assert.assertEquals(Arrays.asList("a", "b", "c"), property.getValue()); + + // check complex array property + ComplexValue value1 = new ComplexValue(); + value1.setTypeName("olingo.odata.test1.CTBase"); + value1.getValue().add(new Property("Edm.Int16", "PropertyInt16", ValueType.PRIMITIVE, 1)); + value1.getValue().add(new Property("Edm.String", "PropertyString", ValueType.PRIMITIVE, "x")); + value1.getValue().add(new Property("Edm.String", "AdditionalPropString", ValueType.PRIMITIVE, "foo")); + ComplexValue value2 = new ComplexValue(); + value2.setTypeName("olingo.odata.test1.CTTwoPrim"); + value2.getValue().add(new Property("Edm.Int16", "PropertyInt16", ValueType.PRIMITIVE, 2)); + value2.getValue().add(new Property("Edm.String", "PropertyString", ValueType.PRIMITIVE, "y")); + ComplexValue value3 = new ComplexValue(); + value3.setTypeName("olingo.odata.test1.CTTwoPrim"); + value3.getValue().add(new Property("Edm.Int16", "PropertyInt16", ValueType.PRIMITIVE, 3)); + value3.getValue().add(new Property("Edm.String", "PropertyString", ValueType.PRIMITIVE, "z")); + property = entity.getProperty("array_complex"); + Assert.assertNotNull(property); + Assert.assertTrue(property.isCollection()); + Assert.assertTrue(property.isComplex()); + Assert.assertEquals(ValueType.COLLECTION_COMPLEX, property.getValueType()); + Assert.assertEquals("olingo.odata.test1.CTTwoPrim", property.getType()); + @SuppressWarnings("unchecked") + List complexValueList = (List) property.getValue(); + Assert.assertNotNull(complexValueList); + Assert.assertEquals(3, complexValueList.size()); + complex = complexValueList.get(0); + Assert.assertNotNull(complex); + List properties = complex.getValue(); + Assert.assertEquals("olingo.odata.test1.CTBase", complex.getTypeName()); + Assert.assertEquals(3, properties.size()); + complexProperty = properties.get(0); + Assert.assertEquals("PropertyInt16", complexProperty.getName()); + Assert.assertEquals("Edm.Int16", complexProperty.getType()); + Assert.assertEquals(ValueType.PRIMITIVE, complexProperty.getValueType()); + Assert.assertEquals((short) 1, complexProperty.getValue()); + complex = complexValueList.get(2); + Assert.assertNotNull(complex); + properties = complex.getValue(); + Assert.assertEquals("olingo.odata.test1.CTTwoPrim", complex.getTypeName()); + Assert.assertEquals(2, properties.size()); + complexProperty = properties.get(1); + Assert.assertEquals("PropertyString", complexProperty.getName()); + Assert.assertEquals("Edm.String", complexProperty.getType()); + Assert.assertEquals(ValueType.PRIMITIVE, complexProperty.getValueType()); + Assert.assertEquals("z", complexProperty.getValue()); + } + + @Test + public void openEntityTwice() { + String payload = "{" + + "\"id\":5," + + "\"PropertyString\":\"foobar\"," + + "\"PropertyString\":\"barfoo\"" + + "}"; + expectException(payload, "OETTwoPrim", DeserializerException.MessageKeys.DUPLICATE_PROPERTY); + } + protected static Entity deserialize(final InputStream stream, final String entityTypeName, final ContentType contentType) throws DeserializerException { return deserializeWithResult(stream, entityTypeName, contentType).getEntity(); diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/xml/ODataXMLDeserializerActionParametersTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/xml/ODataXMLDeserializerActionParametersTest.java index 539b70faf4..785515314c 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/xml/ODataXMLDeserializerActionParametersTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/xml/ODataXMLDeserializerActionParametersTest.java @@ -39,6 +39,8 @@ import org.apache.olingo.server.api.deserializer.DeserializerException; import org.apache.olingo.server.api.deserializer.DeserializerException.MessageKeys; import org.apache.olingo.server.core.deserializer.AbstractODataDeserializerTest; +import org.apache.olingo.server.tecsvc.provider.ActionProvider; +import org.junit.Assert; import org.junit.Test; public class ODataXMLDeserializerActionParametersTest extends AbstractODataDeserializerTest { @@ -214,6 +216,40 @@ public void parameterTwice() throws Exception { "UARTParam", null, MessageKeys.DUPLICATE_PROPERTY); } + @Test + public void openParameter() throws Exception { + String payload = PREAMBLE + + "" + + "foo" + + "32" + + "" + + POSTAMBLE; + + Map parameterMap = deserialize(payload, ActionProvider.nameUARTPrim.getName(), null); + Assert.assertNotNull(parameterMap); + Assert.assertEquals(1, parameterMap.size()); + + Parameter parameter = parameterMap.get("ParameterOCTNoProp"); + Assert.assertNotNull(parameter); + Assert.assertTrue(parameter.isComplex()); + Assert.assertFalse(parameter.isCollection()); + List complexValues = parameter.asComplex().getValue(); + Assert.assertEquals(2, complexValues.size()); + Assert.assertEquals("foo", complexValues.get(0).getValue()); + Assert.assertEquals(32, complexValues.get(1).getValue()); + } + + @Test + public void openComplexTwice() { + String payload = PREAMBLE + + "" + + "foo" + + "bar" + + "" + + POSTAMBLE; + expectException(payload, ActionProvider.nameUARTPrim.getName(), null, MessageKeys.DUPLICATE_PROPERTY); + } + private Parameter deserializeUARTByteNineParam(final String parameterName, final String parameterXmlValue) throws DeserializerException { final Map parameters = deserialize( diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/xml/ODataXmlDeserializerTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/xml/ODataXmlDeserializerTest.java index 06e5df4b5e..f98b7e49e3 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/xml/ODataXmlDeserializerTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/xml/ODataXmlDeserializerTest.java @@ -35,16 +35,13 @@ import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.Link; import org.apache.olingo.commons.api.data.Property; -import org.apache.olingo.commons.api.edm.EdmEntityContainer; -import org.apache.olingo.commons.api.edm.EdmEntitySet; -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.*; +import org.apache.olingo.commons.core.edm.EdmPropertyImpl; import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.deserializer.DeserializerException; import org.apache.olingo.server.api.deserializer.ODataDeserializer; import org.apache.olingo.server.core.deserializer.AbstractODataDeserializerTest; +import org.apache.olingo.server.tecsvc.provider.ComplexTypeProvider; import org.custommonkey.xmlunit.XMLUnit; import org.junit.Assert; import org.junit.BeforeClass; @@ -998,5 +995,54 @@ public void entityReferences() throws Exception { Assert.assertEquals(2, result.size()); Assert.assertEquals("http://host/service/Orders(10643)", result.get(0).toASCIIString()); Assert.assertEquals("http://host/service/Orders(10759)", result.get(1).toASCIIString()); - } + } + + @Test + public void openEntity() throws Exception { + String payload = "" + + "" + + "" + + "" + + "5" + + "foobar" + + "false" + + "3.75" + + "" + + ""; + + EdmEntityType edmEntityType = entityContainer.getEntitySet("OESTwoPrim").getEntityType(); + Entity entity = deserializer.entity(new ByteArrayInputStream(payload.getBytes()), edmEntityType).getEntity(); + + Assert.assertNotNull(entity); + Assert.assertEquals(5, entity.getProperty("id").getValue()); + Assert.assertEquals("foobar", entity.getProperty("PropertyString").getValue()); + Assert.assertEquals(false, entity.getProperty("active").getValue()); + Assert.assertEquals(3.75, entity.getProperty("price").getValue()); + } + + @Test + public void openEntityTwice() { + String payload = "" + + "" + + "" + + "" + + "5" + + "foobar" + + "false" + + "true" + + "" + + ""; + + EdmEntityType edmEntityType = entityContainer.getEntitySet("OESTwoPrim").getEntityType(); + try { + deserializer.entity(new ByteArrayInputStream(payload.getBytes()), edmEntityType); + Assert.fail("Expected exception not thrown"); + } catch (DeserializerException e) { + assertEquals(DeserializerException.MessageKeys.DUPLICATE_PROPERTY, e.getMessageKey()); + } + } } diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java index b156bb9644..a2aeaf4adc 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java @@ -92,6 +92,7 @@ import org.apache.olingo.server.core.serializer.ExpandSelectMock; import org.apache.olingo.server.tecsvc.MetadataETagSupport; import org.apache.olingo.server.tecsvc.data.DataProvider; +import org.apache.olingo.server.tecsvc.provider.ComplexTypeProvider; import org.apache.olingo.server.tecsvc.provider.EdmTechProvider; import org.hamcrest.CoreMatchers; import org.hamcrest.Matcher; @@ -2948,4 +2949,49 @@ public void expandStreamPropertyOnComplexWithNoMetadata() throws Exception { + "\"PropertyComp\":{\"PropertyInt16\":333,\"PropertyString\":\"TEST123\"}}}"; Assert.assertEquals(expectedResult, resultString); } + + @Test + public void openEntity() throws Exception { + EdmEntitySet edmEntitySet = entityContainer.getEntitySet("OESTwoPrim"); + Entity entity = data.readAll(edmEntitySet).getEntities().get(1); + String result = IOUtils.toString(serializer.entity(metadata, edmEntitySet.getEntityType(), entity, + EntitySerializerOptions.with() + .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) + .build()) + .getContent()); + String expected = "{" + + "\"@odata.context\":\"$metadata#OESTwoPrim/$entity\"," + + "\"@odata.metadataEtag\":\"W/\\\"metadataETag\\\"\"," + + "\"id\":2," + + "\"PropertyString\":\"bar\"," + + "\"PropertyBoolean\":true," + + "\"PropertyInt16\":" + Short.MAX_VALUE + + "}"; + Assert.assertEquals(expected, result); + } + + @Test + public void openComplex() throws Exception { + FullQualifiedName complexFqn = ComplexTypeProvider.nameOCTNoProp; + EdmComplexType edmComplexType = metadata.getEdm().getComplexType(complexFqn); + + ComplexValue complex = new ComplexValue(); + complex.setTypeName(complexFqn.getFullQualifiedNameAsString()); + complex.getValue().add(new Property("Edm.Boolean", "dynamic_prop_1", ValueType.PRIMITIVE, Boolean.TRUE)); + complex.getValue().add(new Property("Edm.String", "dynamic_prop_2", ValueType.PRIMITIVE, "foobar")); + complex.getValue().add(new Property("Edm.Int64", "dynamic_prop_3", ValueType.PRIMITIVE, 5)); + + Property property = new Property(complexFqn.getFullQualifiedNameAsString(), "test", ValueType.COMPLEX, complex); + String result = IOUtils.toString(serializer.complex(metadata, edmComplexType, property, + ComplexSerializerOptions.with().contextURL(ContextURL.with().build()).build()).getContent()); + + String expected = "{" + + "\"@odata.context\":\"$metadata\"," + + "\"@odata.metadataEtag\":\"W/\\\"metadataETag\\\"\"," + + "\"dynamic_prop_1\":true," + + "\"dynamic_prop_2\":\"foobar\"," + + "\"dynamic_prop_3\":5" + + "}"; + Assert.assertEquals(expected, result); + } } diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerTest.java index 3321d7db8b..e1ec30d229 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerTest.java @@ -36,13 +36,7 @@ import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.data.ValueType; -import org.apache.olingo.commons.api.edm.EdmComplexType; -import org.apache.olingo.commons.api.edm.EdmEntityContainer; -import org.apache.olingo.commons.api.edm.EdmEntitySet; -import org.apache.olingo.commons.api.edm.EdmEntityType; -import org.apache.olingo.commons.api.edm.EdmPrimitiveType; -import org.apache.olingo.commons.api.edm.EdmProperty; -import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.*; import org.apache.olingo.commons.api.edmx.EdmxReference; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.server.api.OData; @@ -58,6 +52,7 @@ import org.apache.olingo.server.api.serializer.SerializerResult; import org.apache.olingo.server.api.uri.UriHelper; import org.apache.olingo.server.api.uri.UriInfoResource; +import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.api.uri.queryoption.CountOption; import org.apache.olingo.server.api.uri.queryoption.ExpandItem; import org.apache.olingo.server.api.uri.queryoption.ExpandOption; @@ -68,9 +63,13 @@ import org.apache.olingo.server.core.serializer.ExpandSelectMock; import org.apache.olingo.server.core.serializer.json.ODataJsonSerializer; import org.apache.olingo.server.core.uri.UriHelperImpl; +import org.apache.olingo.server.core.uri.UriParameterImpl; import org.apache.olingo.server.tecsvc.MetadataETagSupport; import org.apache.olingo.server.tecsvc.data.DataProvider; +import org.apache.olingo.server.tecsvc.provider.ComplexTypeProvider; import org.apache.olingo.server.tecsvc.provider.EdmTechProvider; +import org.apache.olingo.server.tecsvc.provider.EntityTypeProvider; +import org.apache.olingo.server.tecsvc.provider.FunctionProvider; import org.custommonkey.xmlunit.Diff; import org.custommonkey.xmlunit.Difference; import org.custommonkey.xmlunit.DifferenceListener; @@ -3390,4 +3389,71 @@ public void complexCollectionWithSelectProperty() throws Exception { + ""; Assert.assertEquals(expectedResult, resultString); } + + @Test + public void openEntity() throws Exception { + EdmEntitySet edmEntitySet = entityContainer.getEntitySet("OESTwoPrim"); + Entity entity = data.readAll(edmEntitySet).getEntities().get(1); + long currentMillis = System.currentTimeMillis(); + String result = IOUtils.toString(serializer.entity(metadata, edmEntitySet.getEntityType(), entity, + EntitySerializerOptions.with() + .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) + .build()) + .getContent()); + String expected = "" + + "" + + "OESTwoPrim(2)" + + "" + + "" + + "" + UPDATED_FORMAT.format(new Date(currentMillis)) + "" + + "" + + "" + + "" + + "" + + "" + + "2" + + "bar" + + "true" + + "" + Short.MAX_VALUE + "" + + "" + + "" + + ""; + checkXMLEqual(expected, result); + } + + @Test + public void openComplex() throws Exception { + FullQualifiedName complexFqn = ComplexTypeProvider.nameOCTNoProp; + EdmComplexType edmComplexType = metadata.getEdm().getComplexType(complexFqn); + + ComplexValue complex = new ComplexValue(); + complex.setTypeName(complexFqn.getFullQualifiedNameAsString()); + complex.getValue().add(new Property("Edm.Boolean", "dynamic_prop_1", ValueType.PRIMITIVE, true)); + complex.getValue().add(new Property("Edm.String", "dynamic_prop_2", ValueType.PRIMITIVE, "foobar")); + complex.getValue().add(new Property("Edm.Int64", "dynamic_prop_3", ValueType.PRIMITIVE, 5)); + + Property property = new Property(complexFqn.getFullQualifiedNameAsString(), "test", ValueType.COMPLEX, complex); + String result = IOUtils.toString(serializer.complex(metadata, edmComplexType, property, + ComplexSerializerOptions.with().contextURL(ContextURL.with().build()).build()).getContent()); + + String expected = "" + + "" + + "true" + + "foobar" + + "5" + + ""; + checkXMLEqual(expected, result); + } }