blob: c8b0a2858194a93524ef34154ce99742637baa9d [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2019 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
package org.eclipse.mdm.api.atfxadapter.transaction;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.asam.ods.AIDName;
import org.asam.ods.AIDNameValueSeqUnitId;
import org.asam.ods.AoException;
import org.asam.ods.T_LONGLONG;
import org.eclipse.mdm.api.base.adapter.Attribute;
import org.eclipse.mdm.api.base.adapter.Core;
import org.eclipse.mdm.api.base.adapter.EntityStore;
import org.eclipse.mdm.api.base.adapter.EntityType;
import org.eclipse.mdm.api.base.adapter.Relation;
import org.eclipse.mdm.api.base.model.Deletable;
import org.eclipse.mdm.api.base.model.Entity;
import org.eclipse.mdm.api.base.model.FileLink;
import org.eclipse.mdm.api.base.model.Value;
import org.eclipse.mdm.api.base.query.DataAccessException;
import org.eclipse.mdm.api.odsadapter.query.ODSEntityFactory;
import org.eclipse.mdm.api.odsadapter.utils.ODSConverter;
import org.eclipse.mdm.api.odsadapter.utils.ODSUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Insert statement is used to update entities and their children.
*
* @see org.eclipse.mdm.api.odsadapter.transaction.UpdateStatement
*/
final class UpdateStatement extends BaseStatement {
private static final Logger LOGGER = LoggerFactory.getLogger(UpdateStatement.class);
private final Map<Class<? extends Entity>, List<Entity>> childrenToCreate = new HashMap<>();
private final Map<Class<? extends Entity>, List<Entity>> childrenToUpdate = new HashMap<>();
private final Map<Class<? extends Deletable>, List<Deletable>> childrenToRemove = new HashMap<>();
private Map<String, List<Value>> updateMap = new HashMap<>();
private final List<String> nonUpdatableRelationNames;
private final boolean ignoreChildren;
/**
* Constructor.
*
* @param transaction The owning {@link ATFXTransaction}.
* @param entityType The associated {@link EntityType}.
* @param ignoreChildren If {@code true}, then child entities won't be
* processed.
*/
UpdateStatement(ATFXTransaction transaction, EntityType entityType, boolean ignoreChildren) {
super(transaction, entityType);
nonUpdatableRelationNames = entityType.getInfoRelations().stream().map(Relation::getName)
.collect(Collectors.toList());
this.ignoreChildren = ignoreChildren;
}
/**
* {@inheritDoc}
*/
@Override
public void execute(Collection<Entity> entities) throws AoException, DataAccessException, IOException {
for (Entity entity : entities) {
readEntityCore(ODSEntityFactory.extract(entity));
}
// TODO tracing progress in this method...
List<AIDNameValueSeqUnitId> anvsuList = new ArrayList<>();
T_LONGLONG aID = getEntityType().getODSID();
for (Entry<String, List<Value>> entry : updateMap.entrySet()) {
if (nonUpdatableRelationNames.contains(entry.getKey())) {
// skip "empty" informative relation sequence
continue;
}
Attribute attribute = getEntityType().getAttribute(entry.getKey());
AIDNameValueSeqUnitId anvsu = new AIDNameValueSeqUnitId();
anvsu.attr = new AIDName(aID, entry.getKey());
anvsu.unitId = ODSConverter.toODSLong(0);
anvsu.values = ODSConverter.toODSValueSeq(attribute, entry.getValue());
anvsuList.add(anvsu);
}
long start = System.currentTimeMillis();
getApplElemAccess().updateInstances(anvsuList.toArray(new AIDNameValueSeqUnitId[anvsuList.size()]));
long stop = System.currentTimeMillis();
LOGGER.debug("{} " + getEntityType() + " instances updated in {} ms.", entities.size(), stop - start);
// delete first to make sure naming collisions do not occur!
for (List<Deletable> children : childrenToRemove.values()) {
getTransaction().delete(children);
}
for (List<Entity> children : childrenToCreate.values()) {
getTransaction().create(children);
}
for (List<Entity> children : childrenToUpdate.values()) {
getTransaction().update(children);
}
}
/**
* Reads given {@link Core} and prepares its data to be written:
*
* <ul>
* <li>collect new and removed {@link FileLink}s</li>
* <li>collect property {@link Value}s</li>
* <li>collect foreign key {@code Value}s</li>
* <li>collect child entities for recursive create/update/delete</li>
* </ul>
*
* @param core The {@code Core}.
* @throws DataAccessException Thrown in case of errors.
*/
private void readEntityCore(Core core) throws DataAccessException {
if (!core.getTypeName().equals(getEntityType().getName())) {
throw new IllegalArgumentException("Entity core '" + core.getTypeName()
+ "' is incompatible with current update statement for entity type '" + getEntityType() + "'.");
}
// add all entity values
for (Value value : core.getAllValues().values()) {
updateMap.computeIfAbsent(value.getName(), k -> new ArrayList<>()).add(value);
}
updateMap.computeIfAbsent(getEntityType().getIDAttribute().getName(), k -> new ArrayList<>())
.add(getEntityType().getIDAttribute().createValue(core.getID()));
// define "empty" values for ALL informative relations
for (Relation relation : getEntityType().getInfoRelations()) {
updateMap.computeIfAbsent(relation.getName(), k -> new ArrayList<>()).add(relation.createValue());
}
// preserve "empty" relation values for removed related entities
EntityStore mutableStore = core.getMutableStore();
mutableStore.getRemoved().stream().map(e -> getModelManager().getEntityType(e))
.map(getEntityType()::getRelation).map(Relation::getName).forEach(nonUpdatableRelationNames::remove);
// replace "empty" relation values with corresponding instance IDs
setRelationIDs(mutableStore.getCurrent());
collectChildEntities(core);
getTransaction().addModified(core);
}
/**
* Collects child entities for recursive processing.
*
* @param core The {@link Core}.
*/
private void collectChildEntities(Core core) {
if (ignoreChildren) {
return;
}
for (Entry<Class<? extends Deletable>, List<? extends Deletable>> entry : core.getChildrenStore().getCurrent()
.entrySet()) {
Map<Boolean, List<Entity>> partition = entry.getValue().stream()
.collect(Collectors.partitioningBy(e -> ODSUtils.isValidID(e.getID())));
List<Entity> virtualEntities = partition.get(Boolean.FALSE);
if (virtualEntities != null && !virtualEntities.isEmpty()) {
childrenToCreate.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).addAll(virtualEntities);
}
List<Entity> existingEntities = partition.get(Boolean.TRUE);
if (existingEntities != null && !existingEntities.isEmpty()) {
childrenToUpdate.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).addAll(existingEntities);
}
}
for (Entry<Class<? extends Deletable>, List<? extends Deletable>> entry : core.getChildrenStore().getRemoved()
.entrySet()) {
List<Deletable> toDelete = entry.getValue().stream().filter(e -> ODSUtils.isValidID(e.getID()))
.collect(Collectors.toList());
childrenToRemove.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).addAll(toDelete);
}
}
/**
* Overwrites empty foreign key {@link Value} containers.
*
* @param relatedEntities The related {@link Entity}s.
*/
private void setRelationIDs(Collection<Entity> relatedEntities) {
for (Entity relatedEntity : relatedEntities) {
if (!ODSUtils.isValidID(relatedEntity.getID())) {
throw new IllegalArgumentException("Related entity must be a persited entity.");
}
Relation relation = getEntityType().getRelation(getModelManager().getEntityType(relatedEntity));
List<Value> relationValues = updateMap.get(relation.getName());
if (relationValues == null) {
throw new IllegalStateException("Relation '" + relation
+ "' is incompatible with update statement for entity type '" + getEntityType() + "'");
}
relationValues.get(relationValues.size() - 1).set(relatedEntity.getID());
nonUpdatableRelationNames.remove(relation.getName());
}
}
}