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

com.sap.cds.reflect.impl.CdsModelReader Maven / Gradle / Ivy

There is a newer version: 3.6.1
Show newest version
/*******************************************************************
 * © 2020 SAP SE or an SAP affiliate company. All rights reserved. *
 *******************************************************************/
package com.sap.cds.reflect.impl;

import static com.sap.cds.reflect.impl.CdsStructuredTypeReader.readElementList;
import static com.sap.cds.reflect.impl.CdsUnboundActionAndFunctionReader.readParameterList;
import static com.sap.cds.reflect.impl.CdsUnboundActionAndFunctionReader.readReturnType;
import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.io.CharStreams;
import com.sap.cds.CdsException;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsParameter;
import com.sap.cds.reflect.impl.CdsEntityReader.CdsParameterReader;
import com.sap.cds.reflect.impl.reader.issuecollector.IssueCollector;
import com.sap.cds.reflect.impl.reader.issuecollector.IssueCollectorFactory;
import com.sap.cds.reflect.impl.reader.model.CdsConstants;

public class CdsModelReader implements CdsModel.Reader {
	private static final IssueCollector issueCollector = IssueCollectorFactory.getIssueCollector(CdsModelReader.class);
	private static final ObjectMapper jackson = new ObjectMapper();

	private static final HashFunction hasher = Hashing.goodFastHash(160);
	private static final Cache nonDraftModels = CacheBuilder.newBuilder().maximumSize(100).build();
	private static final Cache draftModels = CacheBuilder.newBuilder().maximumSize(100).build();

	private final CdsModelBuilder cdsModel = CdsModelBuilder.create();

	@Override
	public CdsModel readCsn(InputStream is) {
		return read(is);
	}

	@Override
	public CdsModel readCsn(String csn) {
		return read(csn, false);
	}

	public static CdsModel read(InputStream is) {
		return read(is, false);
	}

	public static CdsModel read(InputStream is, boolean adaptDraftEntities) {
		if (is == null) {
			throw new CdsException("Cannot read CDS model: InputStream must not be null");
		}
		try (final Reader reader = new InputStreamReader(is, UTF_8)) {
			return read(CharStreams.toString(reader), adaptDraftEntities);
		} catch (Exception e) {
			throw new CdsException("Cannot read CDS model: ", e);
		}
	}

	public static CdsModel read(String csn, boolean adaptDraftEntities) {
		return readCached(csn, adaptDraftEntities);
	}

	public static CdsModel read(JsonNode jObject) {
		return read(jObject, false);
	}

	public static CdsModel read(JsonNode jObject, boolean adaptDraftEntities) {
		return readCached(jObject.toString(), adaptDraftEntities);
	}

	private static CdsModel readCached(String csn, boolean adaptDraftEntities) {
		HashCode hash = hasher.hashString(csn, UTF_8);

		Cache cache = adaptDraftEntities ? draftModels : nonDraftModels;
		try {
			return cache.get(hash, () -> parse(csn, adaptDraftEntities));
		} catch (ExecutionException e) {
			throw new CdsException("Cannot load CDS model: ", e);
		}
	}

	private static CdsModel parse(String csn, boolean adaptDraftEntities) {
		try {
			JsonNode jObject = jackson.readTree(csn);

			return new CdsModelReader().parse(jObject, adaptDraftEntities);
		} catch (Exception e) { // NOSONAR
			throw new CdsException("Cannot parse CDS model: ", e);
		}
	}

	private static JsonNode asObject(JsonNode element) {
		if (element != null) {
			return element;
		}
		return new ObjectNode(null);
	}

	private CdsModel parse(JsonNode csn, boolean adaptDraftEntities) {
		JsonNode definitions = asObject(csn.get(CdsConstants.DEFINITIONS));
		Map typeObjects = new HashMap<>();
		Map contextObjects = new HashMap<>();
		Map serviceObjects = new HashMap<>();
		Map entityObjects = new HashMap<>();
		Map actionObjects = new HashMap<>();
		Map functionObjects = new HashMap<>();
		Map eventObjects = new HashMap<>();
		Iterator> fields = definitions.fields();
		DraftAdapter draftAdapter = new DraftAdapter(adaptDraftEntities, entityObjects);

		while (fields.hasNext()) {
			String name = fields.next().getKey();
			JsonNode object = definitions.get(name);
			String kind = object.get(CdsConstants.KIND).asText();
			switch (kind) {
			case CdsConstants.TYPE:
				typeObjects.put(name, object);
				break;
			case CdsConstants.ENTITY:
				entityObjects.put(name, object);
				draftAdapter.processEntity(name, object);
				break;
			case CdsConstants.ACTION:
				actionObjects.put(name, object);
				break;
			case CdsConstants.FUNCTION:
				functionObjects.put(name, object);
				break;
			case CdsConstants.CONTEXT:
				contextObjects.put(name, object);
				break;
			case CdsConstants.SERVICE:
				serviceObjects.put(name, object);
				break;
			case CdsConstants.EVENT:
				eventObjects.put(name, object);
				break;
			default:
				issueCollector.unrecognized(name,
						"The CDS model contains a definition with name '%s' that has an unrecognized type '%s'.", name,
						kind);
			}
		}
		draftAdapter.adaptDraftEntities();
		readMetaInfo(csn);
		readEntities(entityObjects);
		readServices(serviceObjects);
		readUnboundActions(actionObjects);
		readUnboundFunctions(functionObjects);
		readTypes(typeObjects);
		readAnnotations(contextObjects);
		readEvents(eventObjects);

		addElementsToTypes(typeObjects);
		addTypesToArrayedTypes(typeObjects);
		addElementsAndParamsToEntities(entityObjects);
		addElementsToUnboundActions(actionObjects);
		addElementsToUnboundFunctions(functionObjects);
		addElementsToBoundActions(entityObjects);
		addElementsToBoundFunctions(entityObjects);

		return cdsModel.build();
	}

	private void readMetaInfo(JsonNode csn) {
		if (csn.has(CdsConstants.VERSION)) {
			JsonNode version = asObject(csn.get(CdsConstants.VERSION));
			cdsModel.addMeta(CdsConstants.VERSION, version.get(CdsConstants.CSN).asText());
		}
		if (csn.has(CdsConstants.META)) {
			JsonNode meta = asObject(csn.get(CdsConstants.META));
			meta.fields().forEachRemaining(e -> {
				Object value;
				try {
					value = jackson.readValue(e.getValue().toString(), TypeFactory.unknownType());
				} catch (IOException e1) {
					value = e.getValue().toString();
				}
				cdsModel.addMeta(e.getKey(), value);
			});
		}
	}

	private void readEntities(Map entityDefs) {
		for (Entry entry : entityDefs.entrySet()) {
			CdsEntityBuilder entity = CdsEntityReader.read(entry.getKey(), entry.getValue());
			cdsModel.addEntity(entity);
		}
	}

	private void readServices(Map serviceDefs) {
		for (Entry entry : serviceDefs.entrySet()) {
			CdsServiceBuilder service = new CdsServiceBuilder(CdsAnnotationReader.read(entry.getValue()),
					entry.getKey());
			cdsModel.addService(service);
		}
	}

	private void readTypes(Map typeObjects) {
		for (Entry entry : typeObjects.entrySet()) {
			CdsTypeBuilder type = readTypeDefinition(entry.getKey(), entry.getValue(), typeObjects);
			cdsModel.addType(entry.getKey(), type);
		}
	}

	private CdsTypeBuilder readTypeDefinition(String path, JsonNode csn, Map typeDefs) {
		if (csn.has(CdsConstants.ELEMENTS)) {
			// return empty struct as a placeholder
			return CdsStructuredTypeReader.readWithoutElements(path, csn);
		}
		if (csn.has(CdsConstants.ITEMS)) {
			return CdsArrayedTypeReader.readWithoutType(path, csn);
		}
		JsonNode typeName = csn.get(CdsConstants.TYPE);
		if (typeName != null) {
			String type = typeName.asText();
			if (type.equals(CdsConstants.ASSOCIATION) || type.equals(CdsConstants.COMPOSITION)) {
				return new CdsAssociationReader(cdsModel).read(path, csn);
			}
			if (type.startsWith("cds.")) {
				return CdsSimpleTypeReader.read(path, csn);
			}
			if (typeDefs.containsKey(type)) {
				// Resolve types that are based on other custom types
				// only relevant if CSN has not been transformed 4 odata
				// TODO support derived types
				JsonNode jsonNode = typeDefs.get(type);
				return readTypeDefinition(type, jsonNode, typeDefs);
			}
		}
		throw new CdsException("Failed to read type " + path);
	}

	private void readAnnotations(Map contextObjects) {
		contextObjects.forEach((key, value) -> cdsModel.addAnnotations(key, CdsAnnotationReader.read(value)));
	}

	private void addElementsToTypes(Map typeObjects) {
		for (Entry entry : typeObjects.entrySet()) {
			Optional> typeDefinition = cdsModel.findType(entry.getKey());
			if (typeDefinition.isPresent()) {
				CdsTypeBuilder type = typeDefinition.get();
				if (type.isStructured()) {
					CdsStructuredTypeBuilder structuredType = (CdsStructuredTypeBuilder) type;
					List> elements = readElementList(entry.getKey(), entry.getValue(), cdsModel);
					structuredType.addElements(elements);
				}
			}
		}
	}

	private void addTypesToArrayedTypes(Map typeObjects) {
		for (Entry entry : typeObjects.entrySet()) {
			Optional> typeDefinition = cdsModel.findType(entry.getKey());
			if (typeDefinition.isPresent()) {
				CdsTypeBuilder type = typeDefinition.get();
				if (type.isArrayed()) {
					CdsArrayedTypeBuilder arrayedType = (CdsArrayedTypeBuilder) type;
					JsonNode itemsTypeJSON = entry.getValue().get(CdsConstants.ITEMS);
					CdsTypeBuilder itemsType = findType(itemsTypeJSON, cdsModel)
							.orElseGet(() -> readType("", itemsTypeJSON, cdsModel));
					arrayedType.setItemsType(itemsType);
				}
			}
		}
	}

	private void readEvents(Map nodeObjects) {
		for (Entry entry : nodeObjects.entrySet()) {
			CdsEventBuilder event = CdsEventReader.read(entry.getKey(), entry.getValue(), cdsModel);
			cdsModel.addEvent(event);
		}
	}

	private void addElementsAndParamsToEntities(Map entityObjects) {
		for (Entry entry : entityObjects.entrySet()) {
			cdsModel.findEntity(entry.getKey()).ifPresent(entity -> {
				List> elements = readElementList(entry.getKey(), entry.getValue(), cdsModel);
				entity.addElements(elements);

				List params = CdsParameterReader.read(entity.getQualifiedName(), entry.getValue(),
						cdsModel::findType);
				entity.addParams(params);
			});
		}
	}

	private void addElementsToBoundActions(Map entityObjects) {
		Stream boundedActionEntities = cdsModel.concreteEntities()
				.filter(e -> e.actions().findFirst().isPresent());

		boundedActionEntities.forEach(e -> {
			e.actions().forEach(a -> {
				JsonNode actionNode = entityObjects.get(e.getQualifiedName()).get(CdsConstants.ACTIONS)
						.get(a.getQualifiedName());
				List params = readParameterList(a.getQualifiedName(), actionNode, cdsModel);
				a.addParameters(params);
				a.setReturnType(readReturnType(actionNode, cdsModel));
			});
		});
	}

	private void addElementsToBoundFunctions(Map entityObjects) {
		Stream boundedFunctionEntities = cdsModel.concreteEntities()
				.filter(e -> e.functions().findFirst().isPresent());

		boundedFunctionEntities.forEach(e -> {
			e.functions().forEach(f -> {
				JsonNode functionNode = entityObjects.get(e.getQualifiedName()).get(CdsConstants.ACTIONS)
						.get(f.getQualifiedName());
				List params = readParameterList(f.getQualifiedName(), functionNode, cdsModel);
				f.addParameters(params);
				f.setReturnType(readReturnType(functionNode, cdsModel));
			});
		});
	}

	private void addElementsToUnboundActions(Map actionObjects) {
		for (Entry entry : actionObjects.entrySet()) {
			Optional actionOptional = cdsModel.findAction(entry.getKey());
			if (actionOptional.isPresent()) {
				CdsActionBuilder action = actionOptional.get();
				List params = readParameterList(entry.getKey(), entry.getValue(), cdsModel);
				action.addParameters(params);
				action.setReturnType(readReturnType(entry.getValue(), cdsModel));
			}
		}
	}

	private void addElementsToUnboundFunctions(Map functionObjects) {
		for (Entry entry : functionObjects.entrySet()) {
			Optional functionOptional = cdsModel.findFunction(entry.getKey());
			if (functionOptional.isPresent()) {
				CdsFunctionBuilder function = functionOptional.get();
				List params = readParameterList(entry.getKey(), entry.getValue(), cdsModel);
				function.addParameters(params);
				function.setReturnType(readReturnType(entry.getValue(), cdsModel));
			}
		}
	}

	private void readUnboundActions(Map actionDefs) {
		for (Entry entry : actionDefs.entrySet()) {
			CdsActionBuilder action = CdsUnboundActionAndFunctionReader.readAction(entry.getKey(), entry.getValue());
			cdsModel.addAction(action);
		}
	}

	private void readUnboundFunctions(Map functionDefs) {
		for (Entry entry : functionDefs.entrySet()) {
			CdsFunctionBuilder function = CdsUnboundActionAndFunctionReader.readFunction(entry.getKey(),
					entry.getValue());
			cdsModel.addFunction(function);
		}
	}

	public static CdsTypeBuilder readType(String pathToElement, JsonNode csn, CdsModelBuilder model) {
		if (csn.has(CdsConstants.ELEMENTS) || csn.has(CdsConstants.PAYLOAD)) {
			// inline defined struct
			return CdsStructuredTypeReader.read("", csn, model);
		}

		if (csn.has(CdsConstants.ITEMS)) {
			return CdsArrayedTypeReader.read(pathToElement, csn, model);
		}

		JsonNode typeName = csn.get(CdsConstants.TYPE);
		if (typeName != null) {
			String type = typeName.asText();
			if (type.equals(CdsConstants.ASSOCIATION) || type.equals(CdsConstants.COMPOSITION)) {
				// inline defined association
				return new CdsAssociationReader(model).read(pathToElement, csn);
			}
			if (type.startsWith("cds.")) {
				return CdsSimpleTypeReader.read(pathToElement, csn);
			}
		}
		return CdsSimpleTypeReader.read(pathToElement, csn);
	}

	public static Optional> findType(JsonNode csn, CdsModelBuilder model) {
		JsonNode typeName = csn.get(CdsConstants.TYPE);
		if (typeName != null) {
			return model.findType(typeName.asText());
		}
		return Optional.empty();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy