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

com.sap.cds.impl.AssociationLoader Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
/************************************************************************
 * © 2019-2023 SAP SE or an SAP affiliate company. All rights reserved. *
 ************************************************************************/
package com.sap.cds.impl;

import static com.sap.cds.impl.LazyRowImpl.lazyRow;
import static com.sap.cds.impl.builder.model.ExpressionImpl.matching;
import static com.sap.cds.impl.builder.model.StructuredTypeRefImpl.typeRef;
import static com.sap.cds.impl.parser.token.RefSegmentImpl.refSegment;
import static com.sap.cds.util.CdsModelUtils.element;
import static com.sap.cds.util.CdsModelUtils.isSingleValued;
import static com.sap.cds.util.CdsModelUtils.keyNames;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.sap.cds.CdsDataStore;
import com.sap.cds.CdsException;
import com.sap.cds.impl.builder.model.ExpandBuilder;
import com.sap.cds.impl.parser.token.RefSegmentBuilder;
import com.sap.cds.ql.Select;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnExpand;
import com.sap.cds.ql.cqn.CqnReference;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnSelectListItem;
import com.sap.cds.ql.cqn.CqnSelectListValue;
import com.sap.cds.ql.cqn.CqnSortSpecification;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnVisitor;
import com.sap.cds.ql.impl.SelectBuilder;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.util.DataUtils;

public class AssociationLoader {

	private final CdsDataStore dataStore;
	private final CdsStructuredType root;
	private final Map rootValues = new HashMap<>();

	public AssociationLoader(CdsDataStore dataStore, CdsStructuredType root) {
		this.dataStore = dataStore;
		this.root = root;
	}

	public void addValueOfRootEntity(CqnSelectListValue slv, Object value) {
		rootValues.put(slv, value);
	}

	public void expand(CqnExpand expand, Map row) {
		boolean lazy = ((ExpandBuilder) expand).lazy();
		expand(stream(expand.ref()), expand.items(), expand.orderBy(), expand.top(), expand.skip(), expand.alias(), row,
				lazy);
	}

	public void expand(Stream paths, List slis,
			List orderBy, long top, long skip, Optional alias, Map row,
			boolean lazy) {
		if (dataStore != null) {
			List p = paths.collect(Collectors.toList());
			injectors().forEach(i -> i.injectInto(row, p, slis, orderBy, top, skip, alias, lazy));
		}
	}

	private Stream stream(CqnStructuredTypeRef prefix) {
		if (prefix.size() == 1 && prefix.segments().get(0).id().equals("*")) {
			return root.associations().map(a -> typeRef(a.getName()));
		}
		return Stream.of(prefix);
	}

	private Stream injectors() {
		Map> keys = new HashMap<>();
		rootValues.forEach((sli, value) -> {
			if (value != null && sli.isRef() && sli.asRef().size() == 1) {
				Optional keyElement = getKeyElement(sli);
				keyElement.ifPresent(key -> {
					CdsEntity declaringEntity = key.getDeclaringType();
					keys.computeIfAbsent(declaringEntity, e -> new HashMap()).put(key.getName(), value);
				});
			}
		});

		return keys.entrySet().stream().map(e -> new LazyAssociationLoaderInjector(e.getKey(), e.getValue()));
	}

	private Optional getKeyElement(CqnSelectListItem sli) {
		KeyFilter keyFilter = new KeyFilter();
		sli.accept(keyFilter);

		return keyFilter.keyElement();
	}

	private class LazyAssociationLoaderInjector {
		private CdsEntity entity;
		private Map keyValues;

		LazyAssociationLoaderInjector(CdsEntity entity, Map keyValues) {
			this.entity = entity;
			this.keyValues = keyValues;
		}

		private void injectInto(Map row, Collection paths,
				List slis, List orderBy, long top, long skip,
				Optional alias, boolean lazy) {
			paths.forEach(path -> injectInto(row, relativePath(entity, path), slis, orderBy, top, skip, alias, lazy));
		}

		private void injectInto(Map row, CqnStructuredTypeRef path, List slis,
				List orderBy, long top, long skip, Optional alias, boolean lazy) {
			CqnSelect query = query(path, slis, orderBy, top, skip);
			Lazy loader = singleValued(entity, path) ? lazyRow(dataStore, query) : new LazyResultImpl(dataStore, query);
			String displayName = alias.orElse(path.lastSegment());
			if (lazy) {
				DataUtils.putPath(row, displayName, loader);
			} else {
				Object value = loader.loadData();
				boolean createMaps = value != null && !(value instanceof List && ((List) value).isEmpty());
				DataUtils.putPath(row, displayName, value, createMaps);
			}
		}

		private CqnStructuredTypeRef relativePath(CdsEntity entity, CqnStructuredTypeRef path) {
			if (!root.getQualifiedName().equals(entity.getQualifiedName())) {
				List segments = new ArrayList<>(path.size());
				CdsStructuredType e = root;
				boolean add = false;
				for (Segment seg : path.segments()) {
					if (e.findAssociation(seg.id()).isPresent()) {
						e = e.getTargetOf(seg.id());
						if (add) {
							segments.add(seg);
						} else if (e.getQualifiedName().equals(entity.getQualifiedName())) {
							add = true;
						}
					}
				}
				if (segments.isEmpty()) {
					throw new IllegalStateException("Cannot resolve path: " + entity + "." + path);
				}
				return typeRef(segments, null);
			}
			return path;
		}

		private boolean singleValued(CdsEntity entity, CqnStructuredTypeRef path) {
			CdsEntity e = entity;
			CdsElement association = null;
			for (Segment seg : path.segments()) {
				String assocName = seg.id();
				association = e.getAssociation(assocName);
				e = e.getTargetOf(assocName);
			}
			if (association == null) {
				throw new CdsException(
						"Missing association for Entity " + e.getName() + ", under Path " + path.toJson() + ".");
			}
			return isSingleValued(association.getType());
		}

		private CqnSelect query(CqnStructuredTypeRef path, List slis,
				List orderBy, long top, long skip) {
			if (!keyValues.keySet().containsAll(keyNames(entity))) {
				throw new CdsException("Missing key values for entity " + entity.getQualifiedName()
						+ ". Please add all keys to the projection.");
			}
			List segments = new ArrayList<>();
			segments.add(refSegment(entity.getQualifiedName(), matching(keyValues)));
			segments.addAll(path.segments());

			Select query = SelectBuilder.from(typeRef(segments, null)).columns(slis).orderBy(orderBy).limit(top, skip);

			return query;
		}
	}

	private class KeyFilter implements CqnVisitor {
		private CdsElement keyElement;

		@Override
		public void visit(CqnElementRef ref) {
			CdsElement e = element(root, ref);
			if (e != null && e.isKey()) {
				keyElement = e;
			}
		}

		Optional keyElement() {
			return Optional.ofNullable(keyElement);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy