All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cds.adapter.odata.v4.serializer.json.Struct2JsonBuilder Maven / Gradle / Ivy

There is a newer version: 3.2.0
Show newest version
/**************************************************************************
 * (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
 **************************************************************************/
package com.sap.cds.adapter.odata.v4.serializer.json;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;

import org.apache.olingo.commons.api.IConstants;
import org.apache.olingo.commons.api.edm.EdmElement;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.edm.EdmStructuredType;
import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.core.edm.primitivetype.EdmStream;
import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.queryoption.ExpandItem;
import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
import org.apache.olingo.server.api.uri.queryoption.SelectItem;
import org.apache.olingo.server.api.uri.queryoption.SelectOption;
import org.apache.olingo.server.core.serializer.utils.ContextURLBuilder;
import org.apache.olingo.server.core.serializer.utils.ExpandSelectHelper;
import org.apache.olingo.server.core.uri.UriInfoImpl;
import org.apache.olingo.server.core.uri.queryoption.SelectItemImpl;
import org.apache.olingo.server.core.uri.queryoption.SelectOptionImpl;

import com.sap.cds.adapter.odata.v4.serializer.json.api.Data2Json;
import com.sap.cds.adapter.odata.v4.serializer.json.api.PropertyInfo;
import com.sap.cds.adapter.odata.v4.serializer.json.options.NavigationProperty2JsonOptions;
import com.sap.cds.adapter.odata.v4.serializer.json.options.Struct2JsonOptions;
import com.sap.cds.adapter.odata.v4.serializer.json.primitive.String2Json;
import com.sap.cds.adapter.odata.v4.utils.ETagHelper;
import com.sap.cds.adapter.odata.v4.utils.StreamUtils;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.services.utils.ETagUtils;
import com.sap.cds.services.utils.StringUtils;

/**
 * Abstract builder for both complex types and entities.
 */
public abstract class Struct2JsonBuilder {

	protected final Struct2JsonOptions options;
	protected final EdmStructuredType structType;

	protected IConstants constants;
	protected String eTagProperty;
	protected ServiceMetadata metadata;
	protected Set selected = null;
	protected boolean hasExpand;
	protected ExpandItem expandAll;
	protected String resourceName;
	protected ContentType contentType;

	protected Struct2JsonBuilder(Struct2JsonOptions options, EdmStructuredType structType) {
		this.options = options;
		this.structType = structType;
		this.resourceName = options.getContextURL() == null ? null
				: options.getContextURL().getEntitySetOrSingletonOrType();
	}

	protected EdmStructuredType structType() {
		return structType;
	}

	protected void addContextAnnotation(Struct2Json struct2json, boolean withETag) {
		if (!options.isODataMetadataNone()) {
			if (options.getContextURL() != null) { // TODO: check isRootLevel
				// "@context":"$metadata#Travel(to_Customer(CustomerID,FirstName,LastName))/$entity",
				struct2json.context = String2Json.constant(constants.getContext(),
						ContextURLBuilder.create(options.getContextURL()).toASCIIString());
				// "@metadataEtag": // "W/\"bcbb542a7b53905240b\"",
				if (metadata.getServiceMetadataETagSupport() != null
						&& metadata.getServiceMetadataETagSupport().getMetadataETag() != null) {
					struct2json.metadataEtag = String2Json.constant(constants.getMetadataEtag(),
							metadata.getServiceMetadataETagSupport().getMetadataETag());
				}
			}
			eTagProperty = withETag ? ETagHelper.getETagProperty(options.getGlobals(), structType) : null;
		}
	}

	protected void createSelectList() {
		if (!ExpandSelectHelper.isAll(options.getSelect())) {
			selected = ExpandSelectHelper.getSelectedPropertyNames(options.getSelect().getSelectItems());
		}
	}

	protected void addTypeAnnotation(Struct2Json struct2json) {
		// "@type": "#TravelService.Travel"
		final EdmStructuredType resolvedType = StructTypeHelper.resolveEntityType(metadata, structType,
				options.getDerivedTypeName());
		if (options.isODataMetadataFull() ||
				(!options.isODataMetadataNone() && !resolvedType.equals(structType))) {
			struct2json.type = String2Json.constant(constants.getType(),
					"#" + structType.getFullQualifiedName().getFullQualifiedNameAsString());
		}
	}

	protected void createProperties(Struct2Json struct2json) {
		struct2json.primitiveProperties = createPrimitiveAndComplexProperties(struct2json, structType, eTagProperty, selected);
		struct2json.navigationProperties = createNavigationProperties();
	}

	/* Add dynamic properties to a serialization tree. Additionally notice the non-serializable properties */
	public void addDynamicExpandProperties(Struct2Json struct2json, Set potentialDynamicProperties) {
		struct2json.dynamicProperties = new ArrayList<>();
		for (Iterator it = potentialDynamicProperties.iterator(); it.hasNext();) {
			String p = it.next();
			if (structType.getNavigationPropertyNames().contains(p)) {
				EdmElement navigationProperty = structType.getProperty(p);

				NavigationProperty2JsonOptions navOptions = NavigationProperty2JsonOptions
						.with(contentType, options.getODataVersion(), options.getGlobals())
						.contextURL(options.getContextURL())
						.writeOnlyReferences(options.getWriteOnlyReferences())
						.autoExpand(options.isAutoExpand())
						.build();

				struct2json.dynamicProperties
						.add(NavigationProperty2JsonBuilder.createNavigation(navigationProperty, contentType,
								navOptions, false));
				it.remove();
			}
		}

		if (null == struct2json.excludedProperties) {
			struct2json.excludedProperties = potentialDynamicProperties;
		} else {
			struct2json.excludedProperties.addAll(potentialDynamicProperties);
		}
	}

	/* Create navigations via association, complex types not included */
	private List>> createNavigationProperties() {
		List>> navigationProperties = new ArrayList<>(
				structType.getNavigationPropertyNames().size());
		hasExpand = ExpandSelectHelper.hasExpand(options.getExpand());
		if (hasExpand) {
			expandAll = ExpandSelectHelper.getExpandAll(options.getExpand());
		}
		for (String navigationPropertyName : structType.getNavigationPropertyNames()) {
			addNavigationLink(navigationPropertyName);

			if (hasExpand) {
				EdmElement navigationProperty = structType.getProperty(navigationPropertyName);
				ExpandItem innerExpandItem = ExpandSelectHelper.getExpandItemBasedOnType(
						options.getExpand().getExpandItems(),
						navigationPropertyName, structType, resourceName);

				ExpandOption innerExpandOption = null;
				if (innerExpandItem != null) {
					innerExpandOption = innerExpandItem.getExpandOption();
				} else if (expandAll != null) {
					innerExpandOption = expandAll.getExpandOption();
				} else {
					continue;
				}

				NavigationProperty2JsonOptions navOptions = NavigationProperty2JsonOptions
						.with(contentType, options.getODataVersion(), options.getGlobals())
						.contextURL(options.getContextURL())
						.expand(innerExpandOption)
						.count(innerExpandItem == null ? null : innerExpandItem.getCountOption())
						.countPath(innerExpandItem != null && innerExpandItem.hasCountPath())
						.select(innerExpandItem == null ? null : innerExpandItem.getSelectOption())
						.writeOnlyReferences(options.getWriteOnlyReferences())
						.autoExpand(options.isAutoExpand())
						.build();

				navigationProperties
						.add(NavigationProperty2JsonBuilder.createNavigation(navigationProperty, contentType, navOptions, false));
			}
		}
		return navigationProperties;
	}

	/* Create a list of simple and complex properties */
	private List>> createPrimitiveAndComplexProperties(Struct2Json struct2json, EdmStructuredType structType, String eTagProperty,
			Set selected) {
		List>> properties = new ArrayList<>(2 * structType.getPropertyNames().size());
		for (String propertyName : structType.getPropertyNames()) { // we stick to this order
			if (Objects.equals(propertyName, eTagProperty)) {
				struct2json.etag = String2Json.val(constants.getEtag(),
						row -> ETagUtils.createETagHeaderValue(String.valueOf(row.get(propertyName))));
			}
			if (!options.getWriteOnlyReferences() && isSelected(selected, propertyName)) {
				PropertyInfo propertyInfo = PropertyInfo.from((EdmProperty) structType.getProperty(propertyName),
						options.isStrictSerialization());
				if (options.isODataMetadataFull()) {
					Data2Json> propertyMetaSerializer = StructTypeHelper
							.createPropertyMetaTypeSerializer(propertyInfo, options);
					if (propertyMetaSerializer != null) {
						properties.add(propertyMetaSerializer);
					}
				}

				// If stream property is requested inline, include the `mediaContentType`
				// control information: http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4
				// .01.html#sec_ControlInformationmediaodatamedia
				if (propertyInfo.getType() == EdmStream.getInstance()) {
					String entityName = options.getEdmUtils().getCdsEntityName(structType);
					CdsEntity entity = options.getGlobals().getModel().getEntity(entityName);
					String mediaTypeElement = StreamUtils.getCoreMediaTypeElement(entity, propertyName);
					String mediaContentType = StringUtils.isEmpty(mediaTypeElement)
							? StreamUtils.getCoreMediaTypeValue(entity, propertyName)
							: null;

					Function, Object> contentTypeExtractor = row -> {
						Object media = row.get(propertyName);
						if (null != media) {
							if (!StringUtils.isEmpty(mediaTypeElement)) {
								return row.get(mediaTypeElement);
							}
							return mediaContentType;
						}
						return null;
					};
					String2Json> mediaContentTypeInfo = String2Json.val(
							propertyName + constants.getMediaContentType(), contentTypeExtractor);
					properties.add(mediaContentTypeInfo);
				} else if (propertyInfo.getType().getKind() == EdmTypeKind.COMPLEX) {
					NavigationProperty2Json navProperty = createNavigationComplexType(propertyName);
					if (null != navProperty) {
						properties.add(navProperty);
					}
				} else {
					properties.add(StructTypeHelper.createPropertySerializer(propertyInfo, options));
				}
			}
		}
		return properties;
	}

	private NavigationProperty2Json createNavigationComplexType(String navigationPropertyName) {
		EdmElement navigationProperty = structType.getProperty(navigationPropertyName);

		NavigationProperty2JsonOptions navOptions = NavigationProperty2JsonOptions
				.with(contentType, options.getODataVersion(), options.getGlobals())
				.expand(options.getExpand())
				.select(options.getSelect() == null ? null: getSelectOptionComplexType(navigationProperty))
				.build();
		return NavigationProperty2JsonBuilder.createNavigation(navigationProperty, contentType, navOptions, true);
	}

	// Create a reduced copy of select list: $select=el/id,el/name -> $select=id,name
	private SelectOption getSelectOptionComplexType(EdmElement navigationProperty) {
		List selItems = new ArrayList<>();
		SelectOptionImpl selOpt = null;

		for (SelectItem si : options.getSelect().getSelectItems()) {
			List segments = si.getResourcePath().getUriResourceParts();
			// First path segment must be eq to element name
			if (segments.size() > 1 && navigationProperty.getName().equals(segments.get(0).getSegmentValue())) {
				SelectItemImpl i = new SelectItemImpl();
				UriInfoImpl r = new UriInfoImpl();
				segments.stream().skip(1).forEach(r::addResourcePart);
				i.setResourcePath(r);
				selItems.add(i);
			}
		}
		if (!selItems.isEmpty()) {
			selOpt = new SelectOptionImpl();
			selOpt.setSelectItems(selItems);
		}
		return selOpt;
	}

	protected abstract void addNavigationLink(String navigationPropertyName);

	private static boolean isSelected(Set selected, String propertyName) {
		return selected == null || selected.contains(propertyName);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy