blob: f71916cb27124ca0f636463abcbecb98c9fb724a [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 org.asam.ods.AIDName;
import org.asam.ods.AIDNameValueSeqUnitId;
import org.asam.ods.AoException;
import org.asam.ods.ElemId;
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.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.Sortable;
import org.eclipse.mdm.api.base.model.Test;
import org.eclipse.mdm.api.base.model.TestStep;
import org.eclipse.mdm.api.base.model.Value;
import org.eclipse.mdm.api.base.query.Aggregation;
import org.eclipse.mdm.api.base.query.DataAccessException;
import org.eclipse.mdm.api.base.query.Filter;
import org.eclipse.mdm.api.base.query.Query;
import org.eclipse.mdm.api.base.query.Record;
import org.eclipse.mdm.api.base.query.Result;
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 write new entities and their children.
*
* @see org.eclipse.mdm.api.odsadapter.transaction.InsertStatement
*/
final class InsertStatement extends BaseStatement {
private static final Logger LOGGER = LoggerFactory.getLogger(InsertStatement.class);
private final Map<Class<? extends Entity>, List<Entity>> childrenMap = new HashMap<>();
private final List<Core> cores = new ArrayList<>();
private final Map<String, List<Value>> insertMap = new HashMap<>();
private final List<FileLink> fileLinkToUpload = new ArrayList<>();
private final Map<String, SortIndexTestSteps> sortIndexTestSteps = new HashMap<>();
private boolean loadSortIndex;
/**
* Constructor.
*
* @param transaction The owning {@link ATFXTransaction}.
* @param entityType The associated {@link EntityType}.
*/
InsertStatement(ATFXTransaction transaction, EntityType entityType) {
super(transaction, entityType);
loadSortIndex = getModelManager().getEntityType(TestStep.class).equals(getEntityType());
}
/**
* {@inheritDoc}
*/
@Override
public void execute(Collection<Entity> entities) throws AoException, DataAccessException, IOException {
entities.stream().map(ODSEntityFactory::extract).forEach(this::readEntityCore);
execute();
}
/**
* Executes this statement for given {@link Core}s.
*
* @param cores The processed {@code Core}s.
* @throws AoException Thrown if the execution fails.
* @throws DataAccessException Thrown if the execution fails.
* @throws IOException Thrown if a file transfer operation fails.
*/
public void executeWithCores(Collection<Core> cores) throws AoException, DataAccessException, IOException {
cores.forEach(this::readEntityCore);
execute();
}
/**
* Uploads new {@link FileLink}s, adjusts sort indices for new {@link TestStep}
* entities and writes collected insertion data at once. Once new entities are
* written their children are created by delegation to the
* {@link ATFXTransaction}.
*
* @throws AoException Thrown if the execution fails.
* @throws DataAccessException Thrown if the execution fails.
* @throws IOException Thrown if a file transfer operation fails.
*/
private void execute() throws AoException, DataAccessException, IOException {
List<AIDNameValueSeqUnitId> anvsuList = new ArrayList<>();
T_LONGLONG aID = getEntityType().getODSID();
// TODO tracing progress in this method...
if (loadSortIndex && !sortIndexTestSteps.isEmpty()) {
adjustMissingSortIndices();
}
for (Entry<String, List<Value>> entry : insertMap.entrySet()) {
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();
ElemId[] elemIds = getApplElemAccess()
.insertInstances(anvsuList.toArray(new AIDNameValueSeqUnitId[anvsuList.size()]));
for (int i = 0; i < elemIds.length; i++) {
cores.get(i).setID(Long.toString(ODSConverter.fromODSLong(elemIds[i].iid)));
}
long stop = System.currentTimeMillis();
LOGGER.debug("{} " + getEntityType() + " instances created in {} ms.", elemIds.length, stop - start);
for (List<Entity> children : childrenMap.values()) {
getTransaction().create(children);
}
}
/**
* Reads given {@link Core} and prepares its data to be written:
*
* <ul>
* <li>collect new {@link FileLink}s</li>
* <li>trace missing sort indices of TestSteps</li>
* <li>collect property {@link Value}s</li>
* <li>collect foreign key {@code Value}s</li>
* <li>collect child entities for recursive creation</li>
* </ul>
*
* @param core The {@code Core}.
*/
private void readEntityCore(Core core) {
if (!core.getTypeName().equals(getEntityType().getName())) {
throw new IllegalArgumentException("Entity core '" + core.getTypeName()
+ "' is incompatible with current insert statement for entity type '" + getEntityType() + "'.");
}
cores.add(core);
if (loadSortIndex) {
if ((Integer) core.getValues().get(Sortable.ATTR_SORT_INDEX).extract() < 0) {
sortIndexTestSteps.computeIfAbsent(core.getPermanentStore().get(Test.class).getID(),
k -> new SortIndexTestSteps()).testStepCores.add(core);
}
}
// add all entity values
for (Value value : core.getAllValues().values()) {
insertMap.computeIfAbsent(value.getName(), k -> new ArrayList<>()).add(value);
}
// collect file links
fileLinkToUpload.addAll(core.getAddedFileLinks());
// define "empty" values for informative relations
for (Relation relation : getEntityType().getInfoRelations()) {
insertMap.computeIfAbsent(relation.getName(), k -> new ArrayList<>()).add(relation.createValue());
}
// define "empty" values for parent relations
for (Relation relation : getEntityType().getParentRelations()) {
insertMap.computeIfAbsent(relation.getName(), k -> new ArrayList<>()).add(relation.createValue());
}
// replace "empty" relation values with corresponding instance IDs
setRelationIDs(core.getMutableStore().getCurrent());
setRelationIDs(core.getPermanentStore().getCurrent());
for (Entry<Class<? extends Deletable>, List<? extends Deletable>> entry : core.getChildrenStore().getCurrent()
.entrySet()) {
childrenMap.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).addAll(entry.getValue());
}
getTransaction().addModified(core);
getTransaction().addCreated(core);
}
/**
* 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 = insertMap.get(relation.getName());
if (relationValues == null) {
throw new IllegalStateException("Relation '" + relation + "' is incompatible with insert statement "
+ "for entity type '" + getEntityType() + "'");
}
relationValues.get(relationValues.size() - 1).set(relatedEntity.getID());
}
}
/**
* Adjusts missing sort indices for {@link TestStep}s by querying last used max
* sort index.
*
* @throws DataAccessException Thrown if unable to query used sort indices.
*/
private void adjustMissingSortIndices() throws DataAccessException {
EntityType testStep = getEntityType();
EntityType test = getModelManager().getEntityType(Test.class);
Relation parentRelation = testStep.getRelation(test);
Attribute attrSortIndex = testStep.getAttribute(Sortable.ATTR_SORT_INDEX);
Query query = getQueryService().createQuery().select(parentRelation.getAttribute())
.select(attrSortIndex, Aggregation.MAXIMUM).group(parentRelation.getAttribute());
Filter filter = Filter.idsOnly(parentRelation, sortIndexTestSteps.keySet());
for (Result result : query.fetch(filter)) {
Record record = result.getRecord(testStep);
int sortIndex = result.getValue(attrSortIndex, Aggregation.MAXIMUM).extract();
sortIndexTestSteps.remove(record.getID(parentRelation).get()).setIndices(sortIndex + 1);
}
// start at 1 for all remaining
sortIndexTestSteps.values().forEach(tss -> tss.setIndices(0));
}
/**
* Utility class to write missing sort index of new {@link TestStep}s.
*/
private static final class SortIndexTestSteps {
private List<Core> testStepCores = new ArrayList<>();
/**
* Assigns sort indices to {@link Core}s starting at given index.
*
* @param startIndex The start index.
*/
private void setIndices(int startIndex) {
int index = startIndex;
for (Core core : testStepCores) {
core.getValues().get(Sortable.ATTR_SORT_INDEX).set(index++);
}
}
}
}