Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sap.cds.ql.impl.DeepInsertSplitter Maven / Gradle / Ivy
/************************************************************************
* © 2019-2022 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.ql.impl;
import static com.google.common.collect.Lists.reverse;
import static com.sap.cds.util.CdsModelUtils.isCascading;
import static com.sap.cds.util.CdsModelUtils.isReverseAssociation;
import static com.sap.cds.util.CdsModelUtils.CascadeType.INSERT;
import static com.sap.cds.util.DataUtils.isFkUpdate;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.Map.Entry;
import com.sap.cds.SessionContext;
import com.sap.cds.ql.CdsDataException;
import com.sap.cds.ql.Insert;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.util.CdsModelUtils;
import com.sap.cds.util.OnConditionAnalyzer;
public class DeepInsertSplitter {
private final CdsEntity entity;
private final Map>> insertEntries = new LinkedHashMap<>();
private final SessionContext sessionContext;
public DeepInsertSplitter(CdsModel model, String entityName, SessionContext sessionContext) {
this(model.getEntity(entityName), sessionContext);
}
public DeepInsertSplitter(CdsEntity entity, SessionContext sessionContext) {
this.entity = entity;
this.sessionContext = sessionContext;
}
public List split(List> entries) {
entries.forEach(entry -> flattenEntry(entity, entry));
return computeInserts();
}
public List split(Map entry) {
flattenEntry(entity, entry);
return computeInserts();
}
private List computeInserts() {
List inserts = insertEntries.entrySet().stream().map(e -> {
String entity = e.getKey();
return Insert.into(entity).entries(e.getValue());
}).collect(toList());
return reverse(inserts);
}
private void flattenEntry(CdsEntity entity, Map entry) {
Map flatEntry = new HashMap<>();
String entityName = entity.getName();
Iterator> iter = entry.entrySet().iterator();
while (iter.hasNext()) {
Entry e = iter.next();
String elementName = e.getKey();
Object value = e.getValue();
// TODO: Re-factor: Use find element instead of association after deprecation of
// V1 (non-OData)
Optional assoc = entity.findAssociation(elementName);
if (assoc.isPresent()) {
if (value != null) {
CdsElement association = assoc.get();
if (isReverseAssociation(association)) {
handleReverseAssociation(entity, association, entry, value);
} else {
handleForwardAssociation(flatEntry, entityName, value, association);
}
}
} else {
Optional element = entity.findElement(elementName);
if (element.isPresent() && element.get().getType().isStructured()) {
CdsElement structElement = element.get();
if (value != null) {
flatEntry.put(structElement.getName(), collectStructElement(value, structElement));
}
} else if (flatEntry.containsKey(elementName)) {
// override flat FK with struct value
e.setValue(flatEntry.get(elementName));
} else {
flatEntry.put(elementName, value);
}
}
}
insertEntries.computeIfAbsent(entity.getQualifiedName(), k -> new ArrayList<>()).add(flatEntry);
}
private void handleForwardAssociation(Map flatEntry, String entityName, Object value,
CdsElement association) {
Map targetValues = asMap(entityName, association.getName(), value);
flatEntry.putAll(computeFkValues(association, targetValues));
if (cascadeInsert(association)) {
assertInputDataContainsKeys(association, targetValues);
CdsAssociationType assocType = association.getType();
flattenEntry(assocType.getTarget(), targetValues);
} else { // remove non-FK values from result
Set assocKeys = CdsModelUtils.assocKeys(association);
targetValues.keySet().retainAll(assocKeys);
}
}
@SuppressWarnings("unchecked")
private Map collectStructElement(Object value, CdsElement element) {
Map flatEntry = new HashMap<>();
Map valueMap = (Map) value;
CdsStructuredType structElement = element.getType().as(CdsStructuredType.class);
structElement.associations().filter(f -> valueMap.keySet().contains(f.getName())).forEach(assoc -> {
handleForwardAssociation(flatEntry, element.getDeclaringType().getName(), valueMap.get(assoc.getName()),
assoc);
});
structElement.nonAssociationElements().filter(f -> valueMap.keySet().contains(f.getName())).forEach(e -> {
if (e.getType().isStructured()) {
flatEntry.put(e.getName(), collectStructElement(valueMap.get(e.getName()), e));
} else {
flatEntry.put(e.getName(), valueMap.get(e.getName()));
}
});
return flatEntry;
}
private void handleReverseAssociation(CdsEntity entity, CdsElement association, Map entry,
Object targetVal) {
if (cascadeInsert(association)) {
CdsAssociationType assoc = association.getType();
Map fkValues = new OnConditionAnalyzer(association, true, sessionContext)
.getFkValues(entry);
List> targetValues = asList(entity.getName(), association.getName(), targetVal);
for (Map child : targetValues) {
child.putAll(fkValues);
flattenEntry(assoc.getTarget(), child);
}
} else {
// TODO set reference from association target to entity
throw new UnsupportedOperationException("Cannot set reference " + entity.getQualifiedName() + "."
+ association.getName() + ". Reverse associations are not supported.");
}
}
private static boolean cascadeInsert(CdsElement element) {
return isCascading(INSERT, element);
}
private Map computeFkValues(CdsElement assoc, Map targetValues) {
return new OnConditionAnalyzer(assoc, false, sessionContext).getFkValues(targetValues);
}
private void assertInputDataContainsKeys(CdsElement association, Map targetValues) {
CdsAssociationType assoc = association.getType();
Set keyNames = CdsModelUtils.keyNames(assoc.getTarget());
if (!targetValues.keySet().containsAll(keyNames)) {
if (isFkUpdate(association, targetValues, sessionContext)) {
return; // update of FK targeting non-key element
}
throw new CdsDataException(
"Data set " + targetValues.keySet() + " for association " + association.getQualifiedName()
+ " does not contain values for all target entity keys " + keyNames + ".");
}
}
@SuppressWarnings("unchecked")
private static Map asMap(String entityName, String associationName, Object value) {
try {
return (Map) value;
} catch (ClassCastException ex) {
throw badValue(entityName, associationName, ex);
}
}
@SuppressWarnings("unchecked")
private static List> asList(String entityName, String associationName, Object value) {
if (value instanceof List) {
return (List>) value;
}
if (value instanceof Map) {
return singletonList((Map) value);
}
throw badValue(entityName, associationName, null);
}
private static RuntimeException badValue(String entityName, String associationName, RuntimeException cause) {
return new CdsDataException("Unexpected value: Entity '" + entityName
+ "' contains unexpected value for the association '" + associationName + "'. ", cause);
}
}