| /******************************************************************************** |
| * 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); |
| } |
| |
| } |