blob: a03f137c90a6f998240e94156c75f30e10164c9f [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2018 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import org.asam.ods.AoException;
import org.asam.ods.T_LONGLONG;
import org.eclipse.mdm.api.base.adapter.EntityType;
import org.eclipse.mdm.api.base.adapter.Relation;
import org.eclipse.mdm.api.base.model.Channel;
import org.eclipse.mdm.api.base.model.ContextRoot;
import org.eclipse.mdm.api.base.model.ContextType;
import org.eclipse.mdm.api.base.model.Entity;
import org.eclipse.mdm.api.base.model.FilesAttachable;
import org.eclipse.mdm.api.base.model.Measurement;
import org.eclipse.mdm.api.base.model.ParameterSet;
import org.eclipse.mdm.api.base.model.TestStep;
import org.eclipse.mdm.api.base.query.DataAccessException;
import org.eclipse.mdm.api.base.query.Filter;
import org.eclipse.mdm.api.base.query.JoinType;
import org.eclipse.mdm.api.base.query.Query;
import org.eclipse.mdm.api.base.query.Result;
import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig;
import org.eclipse.mdm.api.odsadapter.query.ODSEntityType;
import org.eclipse.mdm.api.odsadapter.utils.ODSConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Delete statement is used to delete entities with their children.
*
* @see org.eclipse.mdm.api.odsadapter.transaction.DeleteStatement
*/
final class DeleteStatement extends BaseStatement {
private static final List<String> AUTO_DELETABLE = Arrays.asList("MeaQuantity", "SubMatrix", "LocalColumn",
"ExternalComponent");
private static final Logger LOGGER = LoggerFactory.getLogger(DeleteStatement.class);
private final boolean useAutoDelete;
/**
* Constructor.
*
* @param transaction
* The owning {@link ATFXTransaction}.
* @param entityType
* The associated {@link EntityType}.
* @param useAutoDelete
* If {@code true} child relations of {@link Measurement}
* entities are not followed.
*/
DeleteStatement(ATFXTransaction transaction, EntityType entityType, boolean useAutoDelete) {
super(transaction, entityType);
this.useAutoDelete = useAutoDelete;
}
/**
* {@inheritDoc}
*/
@Override
public void execute(Collection<Entity> entities) throws AoException, DataAccessException {
if (entities.stream().filter(e -> !e.getTypeName().equals(getEntityType().getName())).findAny().isPresent()) {
throw new IllegalArgumentException("At least one given entity is of incompatible type.");
}
long start = System.currentTimeMillis();
int amount = delete(getEntityType(), entities.stream().map(Entity::getID).collect(Collectors.toSet()), false);
LOGGER.debug("{} instances deleted in {} ms.", amount, System.currentTimeMillis() - start);
}
/**
* Recursively follows child relations of given entities and deletes all
* child entities before deleting parent entities.
*
* @param entityType
* {@link EntityType} of the deleted entities.
* @param instanceIDs
* Instance IDs of entities which have to be deleted.
* @param ignoreSiblings
* Is it required to check whether {@link Measurement} siblings
* share a common {@link ContextRoot}s.
* @return Returns the total number of deleted instances.
* @throws AoException
* Thrown if unable to delete entities.
* @throws DataAccessException
* Thrown if unable to query child entities.
*/
private int delete(EntityType entityType, Collection<String> instanceIDs, boolean ignoreSiblings)
throws AoException, DataAccessException {
if (instanceIDs.isEmpty()) {
return 0;
}
Query query = getQueryService().createQuery().selectID(entityType);
for (Relation relation : entityType.getChildRelations()) {
if (useAutoDelete && AUTO_DELETABLE.contains(relation.getTarget().getName())) {
continue;
}
if (!relation.getTarget().equals(relation.getSource())) {
query.join(relation, JoinType.OUTER).selectID(relation.getTarget());
}
}
// select attributes containing file links only for entity types
// implementing FilesAttachable
EntityConfig<?> entityConfig = getModelManager().getEntityConfig(entityType);
if (FilesAttachable.class.isAssignableFrom(entityConfig.getEntityClass())) {
entityType.getAttributes().stream().filter(a -> a.getValueType().isFileLinkType()).forEach(query::select);
}
EntityType testStep = getModelManager().getEntityType(TestStep.class);
EntityType measurement = getModelManager().getEntityType(Measurement.class);
EntityType channel = getModelManager().getEntityType(Channel.class);
EntityType unitUnderTest = getModelManager().getEntityType(ContextRoot.class, ContextType.UNITUNDERTEST);
EntityType testSequence = getModelManager().getEntityType(ContextRoot.class, ContextType.TESTSEQUENCE);
EntityType testEquipment = getModelManager().getEntityType(ContextRoot.class, ContextType.TESTEQUIPMENT);
// type in this list must be deleted AFTER this this instances have been
// deleted
// informative relation is considered as a child relation
List<EntityType> delayedDelete = new ArrayList<>();
// join context roots
if (measurement.equals(entityType) || testStep.equals(entityType)) {
query.join(entityType.getRelation(unitUnderTest), JoinType.OUTER).selectID(unitUnderTest);
query.join(entityType.getRelation(testSequence), JoinType.OUTER).selectID(testSequence);
query.join(entityType.getRelation(testEquipment), JoinType.OUTER).selectID(testEquipment);
delayedDelete.addAll(Arrays.asList(unitUnderTest, testSequence, testEquipment));
}
// join parameter sets
if (measurement.equals(entityType) || channel.equals(entityType)) {
EntityType parameterSet = getModelManager().getEntityType(ParameterSet.class);
query.join(entityType.getRelation(parameterSet), JoinType.OUTER).selectID(parameterSet);
}
Filter filter = Filter.or().ids(entityType, instanceIDs);
entityType.getParentRelations().stream().filter(r -> r.getTarget().equals(entityType))
.forEach(relation -> filter.ids(relation, instanceIDs));
// query child IDs
Map<EntityType, Set<String>> children = new HashMap<>();
for (Result result : query.fetch(filter)) {
// load children of other types
result.stream().filter(r -> r.getID() != null && r.getID().length() > 0).forEach(r -> {
children.computeIfAbsent(r.getEntityType(), k -> new HashSet<>()).add(r.getID());
});
}
// omit context roots with references to not removed measurements
if (!ignoreSiblings && measurement.equals(entityType)) {
for (EntityType contextRoot : Arrays.asList(unitUnderTest, testSequence, testEquipment)) {
Set<String> contextRootIDs = children.getOrDefault(contextRoot, Collections.emptySet());
if (contextRootIDs.isEmpty()) {
continue;
}
Query contextQuery = getQueryService().createQuery();
contextQuery.selectID(contextRoot, measurement);
contextQuery.join(contextRoot, measurement);
for (Result result : contextQuery.fetch(Filter.idsOnly(contextRoot, contextRootIDs))) {
if (instanceIDs.contains(result.getRecord(measurement).getID())) {
continue;
}
// context root references a not removed measurement
contextRootIDs.remove(result.getRecord(contextRoot).getID());
}
}
}
int amount = 0;
// delete real children
List<Entry<EntityType, Set<String>>> consideredChildren = new ArrayList<>();
for (Entry<EntityType, Set<String>> entry : children.entrySet()) {
EntityType childType = entry.getKey();
Set<String> childInstanceIDs = entry.getValue();
if (entityType.equals(childType)) {
childInstanceIDs.removeAll(instanceIDs);
} else if (delayedDelete.contains(entry.getKey())) {
consideredChildren.add(entry);
continue;
}
amount += delete(entry.getKey(), childInstanceIDs, true);
}
getApplElemAccess().deleteInstances(((ODSEntityType) entityType).getODSID(), toODSIDs(instanceIDs));
// delete considered children (informative relation)
for (Entry<EntityType, Set<String>> entry : consideredChildren) {
amount += delete(entry.getKey(), entry.getValue(), true);
}
return amount + instanceIDs.size();
}
/**
* Converts given {@code Collection} of instance IDs to ODS a
* {@link T_LONGLONG} array.
*
* @param instanceIDs
* The instance IDs.
* @return The corresponding ODS {@code T_LONGLONG[]} is returned.
*/
private T_LONGLONG[] toODSIDs(Collection<String> instanceIDs) {
return instanceIDs.stream().map(ODSConverter::toODSID).toArray(T_LONGLONG[]::new);
}
}