blob: 06da14658cf18f98bdc13ba3388d7bcd6c34f1bf [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2020 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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.management.ServiceNotFoundException;
import org.asam.ods.AoException;
import org.asam.ods.ApplicationStructure;
import org.asam.ods.ElemId;
import org.asam.ods.InstanceElement;
import org.asam.ods.T_LONGLONG;
import org.eclipse.mdm.api.atfxadapter.transaction.ATFXTransaction;
import org.eclipse.mdm.api.base.ServiceNotProvidedException;
import org.eclipse.mdm.api.base.Transaction;
import org.eclipse.mdm.api.base.adapter.EntityType;
import org.eclipse.mdm.api.base.adapter.Relation;
import org.eclipse.mdm.api.base.massdata.ReadRequest;
import org.eclipse.mdm.api.base.model.Channel;
import org.eclipse.mdm.api.base.model.ChannelGroup;
import org.eclipse.mdm.api.base.model.ContextComponent;
import org.eclipse.mdm.api.base.model.ContextDescribable;
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.Environment;
import org.eclipse.mdm.api.base.model.MeasuredValues;
import org.eclipse.mdm.api.base.model.StatusAttachable;
import org.eclipse.mdm.api.base.model.Test;
import org.eclipse.mdm.api.base.model.TestStep;
import org.eclipse.mdm.api.base.model.User;
import org.eclipse.mdm.api.base.model.Value;
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.QueryService;
import org.eclipse.mdm.api.base.query.Record;
import org.eclipse.mdm.api.base.query.Result;
import org.eclipse.mdm.api.dflt.EntityManager;
import org.eclipse.mdm.api.dflt.model.Classification;
import org.eclipse.mdm.api.dflt.model.EntityFactory;
import org.eclipse.mdm.api.dflt.model.ExtSystemAttribute;
import org.eclipse.mdm.api.dflt.model.MDMAttribute;
import org.eclipse.mdm.api.dflt.model.Status;
import org.eclipse.mdm.api.dflt.model.TemplateTest;
import org.eclipse.mdm.api.dflt.model.TemplateTestStep;
import org.eclipse.mdm.api.odsadapter.ODSContext;
import org.eclipse.mdm.api.odsadapter.ODSContextFactory;
import org.eclipse.mdm.api.odsadapter.ODSEntityManager;
import org.eclipse.mdm.api.odsadapter.ReadRequestHandler;
import org.eclipse.mdm.api.odsadapter.lookup.EntityLoader;
import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig.Key;
import org.eclipse.mdm.api.odsadapter.query.ODSEntityFactory;
import org.eclipse.mdm.api.odsadapter.query.ODSEntityType;
import org.eclipse.mdm.api.odsadapter.query.ODSModelManager;
import org.eclipse.mdm.api.odsadapter.utils.ODSConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Strings;
/**
* ASAM ODS implementation of the {@link EntityManager} interface.
*
* @see ODSEntityManager
*/
public class ATFXEntityManager implements EntityManager {
private static final String AA_ID = "Id";
private static final String AE_TESTSTEP = "TestStep";
private static final String AE_TEST = "Test";
private static final String AE_STRUCTURE_LEVEL = "StructureLevel";
private static final String AE_PROJECT = "Project";
private static final Logger LOGGER = LoggerFactory.getLogger(ATFXEntityManager.class);
private static final String PROP_ASAMPATH = "org.eclipse.mdm.api.atfxadapter.asampath";
private static final Pattern EXTSYSTEMCOMP_REGEX = Pattern.compile("\\[(.+)\\].*", Pattern.DOTALL);
private static final Pattern EXTSYSTEMATTR_REGEX = Pattern.compile("\\[.*\\](.+)", Pattern.DOTALL);
private final ATFXContext context;
private final ODSModelManager odsModelManager;
private final QueryService queryService;
private final EntityLoader entityLoader;
private final List<ExtSystemAttribute> extSystemAttrList;
private NameHelper nameHelper;
/**
* Constructor.
*
* @param context The {@link ODSContext}.
* @param extSystemAttrList
* @throws ServiceNotFoundException
*/
public ATFXEntityManager(ATFXContext context, List<ExtSystemAttribute> extSystemAttrList) {
this.context = context;
this.odsModelManager = context.getODSModelManager();
this.queryService = context.getQueryService()
.orElseThrow(() -> new ServiceNotProvidedException(QueryService.class));
this.extSystemAttrList = extSystemAttrList;
entityLoader = new EntityLoader(odsModelManager, queryService);
}
/**
* {@inheritDoc}
*/
@Override
public Environment loadEnvironment() throws DataAccessException {
List<Environment> environments = loadAll(Environment.class);
if (environments.size() != 1) {
throw new DataAccessException("Unable to laod the environment entity.");
}
return environments.get(0);
}
/**
* {@inheritDoc}
*/
@Override
public Optional<User> loadLoggedOnUser() throws DataAccessException {
return Optional.empty();
}
/**
* {@inheritDoc}
*/
@Override
public <T extends Entity> List<T> load(Class<T> entityClass, Collection<String> instanceIDs)
throws DataAccessException {
return entityLoader.loadAll(new Key<>(entityClass), instanceIDs);
}
/**
* {@inheritDoc}
*/
@Override
public <T extends Entity> List<T> load(Class<T> entityClass, ContextType contextType,
Collection<String> instanceIDs) throws DataAccessException {
return entityLoader.loadAll(new Key<>(entityClass, contextType), instanceIDs);
}
/**
* {@inheritDoc}
*/
@Override
public <T extends Entity> Optional<T> loadParent(Entity child, Class<T> entityClass) throws DataAccessException {
EntityType parentEntityType = odsModelManager.getEntityType(entityClass);
EntityType childEntityType = odsModelManager.getEntityType(child);
Optional<String> instanceID = Optional.empty();
if (child instanceof Channel && ChannelGroup.class.equals(entityClass)) {
// this covers the gap between channel and channel group via local column
EntityType localColumnEntityType = odsModelManager.getEntityType("LocalColumn");
Relation relLocalColumnToChannel = localColumnEntityType.getRelation(childEntityType);
Relation relLocalColumnToChannelGroup = localColumnEntityType.getRelation(parentEntityType);
Optional<String> localColumnID = queryService.createQuery().select(localColumnEntityType.getIDAttribute())
.fetchSingleton(Filter.idOnly(relLocalColumnToChannel, child.getID()))
.map(r -> r.getRecord(localColumnEntityType)).map(r -> r.getID());
if (localColumnID.isPresent()) {
instanceID = queryService.createQuery().select(relLocalColumnToChannelGroup.getAttribute())
.fetchSingleton(Filter.idOnly(localColumnEntityType, localColumnID.get()))
.map(r -> r.getRecord(localColumnEntityType))
.map(r -> r.getID(relLocalColumnToChannelGroup).get());
}
} else {
Relation relationToParent = childEntityType.getRelation(parentEntityType);
Query query = queryService.createQuery().select(relationToParent.getAttribute());
instanceID = query.fetchSingleton(Filter.idOnly(childEntityType, child.getID()))
.map(r -> r.getRecord(childEntityType)).map(r -> r.getID(relationToParent).get());
}
if (instanceID.isPresent()) {
return Optional.of(entityLoader.load(new Key<>(entityClass), instanceID.get()));
}
return Optional.empty();
}
/**
* {@inheritDoc}
*/
@Override
public <T extends Entity> List<T> loadAll(Class<T> entityClass, String pattern) throws DataAccessException {
return entityLoader.loadAll(new Key<>(entityClass), pattern);
}
/**
* {@inheritDoc}
*/
@Override
public <T extends StatusAttachable> List<T> loadAll(Class<T> entityClass, Status status, String pattern)
throws DataAccessException {
EntityType entityType = odsModelManager.getEntityType(entityClass);
EntityType classificationType = odsModelManager.getEntityType(Classification.class);
EntityType statusEntityType = odsModelManager.getEntityType(status.getTypeName());
List<String> instanceIDs = queryService.createQuery().join(entityType, classificationType)
.join(classificationType, statusEntityType).selectID(entityType)
.fetch(Filter.and().id(statusEntityType, status.getID()).name(entityType, pattern)).stream()
.map(r -> r.getRecord(entityType)).map(Record::getID).collect(Collectors.toList());
return entityLoader.loadAll(new Key<>(entityClass), instanceIDs);
}
/**
* {@inheritDoc}
*/
@Override
public <T extends Entity> List<T> loadAll(Class<T> entityClass, ContextType contextType, String pattern)
throws DataAccessException {
return entityLoader.loadAll(new Key<>(entityClass, contextType), pattern);
}
/**
* {@inheritDoc}
*/
@Override
public <T extends Entity> List<T> loadChildren(Entity parent, Class<T> entityClass, String pattern)
throws DataAccessException {
if (!Strings.isNullOrEmpty(pattern) && !"*".equals(pattern)) {
throw new DataAccessException("Pattern not supported by ATFXAdapter");
}
EntityType parentEntityType = odsModelManager.getEntityType(parent);
EntityType childEntityType = odsModelManager.getEntityType(entityClass);
List<String> instanceIDs;
if (parent instanceof ChannelGroup && Channel.class.equals(entityClass)) {
// this covers the gap between channel and channel group via local column
EntityType localColumnEntityType = odsModelManager.getEntityType("LocalColumn");
Relation relLocalColumnToChannel = localColumnEntityType.getRelation(childEntityType);
Relation relLocalColumnToChannelGroup = localColumnEntityType.getRelation(parentEntityType);
instanceIDs = queryService.createQuery().select(relLocalColumnToChannel.getAttribute())
.fetch(Filter.and().id(relLocalColumnToChannelGroup, parent.getID()).name(localColumnEntityType,
pattern))
.stream().map(r -> r.getRecord(localColumnEntityType))
.map(r -> r.getID(relLocalColumnToChannel).get()).collect(Collectors.toList());
return entityLoader.loadAll(new Key<>(entityClass), instanceIDs);
} else {
Relation relationToParent = childEntityType.getRelation(parentEntityType);
instanceIDs = queryService.createQuery().selectID(childEntityType).select(relationToParent.getAttribute())
.fetch(Filter.and().id(relationToParent, parent.getID()).name(childEntityType, pattern)).stream()
.map(r -> r.getRecord(childEntityType)).map(Record::getID).collect(Collectors.toList());
}
return entityLoader.loadAll(new Key<>(entityClass), instanceIDs);
}
/**
* {@inheritDoc}
*/
@Override
public List<ContextType> loadContextTypes(ContextDescribable contextDescribable) throws DataAccessException {
EntityType contextDescribableEntityType = odsModelManager.getEntityType(contextDescribable);
Query query = queryService.createQuery();
Map<ContextType, EntityType> contextRootEntityTypes = new EnumMap<>(ContextType.class);
for (ContextType contextType : ContextType.values()) {
EntityType entityType = odsModelManager.getEntityType(ContextRoot.class, contextType);
contextRootEntityTypes.put(contextType, entityType);
query.join(contextDescribableEntityType.getRelation(entityType), JoinType.OUTER).selectID(entityType);
}
Optional<Result> result = query
.fetchSingleton(Filter.idOnly(contextDescribableEntityType, contextDescribable.getID()));
if (result.isPresent()) {
List<ContextType> contextTypes = new ArrayList<>();
for (Entry<ContextType, EntityType> entry : contextRootEntityTypes.entrySet()) {
Optional<String> instanceID = result.map(r -> r.getRecord(entry.getValue())).map(Record::getID);
if (instanceID.isPresent()) {
contextTypes.add(entry.getKey());
}
}
return contextTypes;
}
return Collections.emptyList();
}
/**
* {@inheritDoc}
*/
@Override
public Map<ContextType, ContextRoot> loadContexts(ContextDescribable contextDescribable,
ContextType... contextTypes) throws DataAccessException {
EntityType contextDescribableEntityType = odsModelManager.getEntityType(contextDescribable);
Map<ContextType, EntityType> contextRootEntityTypes = new EnumMap<>(ContextType.class);
Map<ContextType, ContextRoot> contextRoots = new EnumMap<>(ContextType.class);
for (ContextType contextType : contextTypes.length == 0 ? ContextType.values() : contextTypes) {
EntityType entityType = odsModelManager.getEntityType(ContextRoot.class, contextType);
contextRootEntityTypes.put(contextType, entityType);
// openATFX does not support joins in queries, thus we have to retrive the
// ContextRoots one by one by ID
ElemId elemId = new ElemId(((ODSEntityType) contextDescribableEntityType).getODSID(),
ODSConverter.toODSID(contextDescribable.getID()));
try {
Optional<Relation> relation = getRelation(contextDescribableEntityType, entityType);
if (relation.isPresent()) {
T_LONGLONG[] contextRootIds = odsModelManager.getApplElemAccess().getRelInst(elemId,
relation.get().getName());
if (contextRootIds != null && contextRootIds.length > 0) {
// There can only be one result per ContextType, thus we take the first one and
// ignore the rest
String instanceID = Long.toString(ODSConverter.fromODSLong(contextRootIds[0]));
contextRoots.put(contextType,
entityLoader.load(new Key<>(ContextRoot.class, contextType), instanceID));
}
}
} catch (AoException e) {
throw new DataAccessException("Cannot load contextRoot '" + entityType.getName() + "' for ID '"
+ contextDescribable.getID() + "': " + e.reason, e);
}
}
if (extSystemAttrList != null) {
contextRoots = mapAttributesWithExtSystem(contextRoots);
}
return contextRoots;
}
private Optional<Relation> getRelation(EntityType contextDescribableEntityType, EntityType entityType) {
try {
return Optional.of(contextDescribableEntityType.getRelation(entityType));
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}
private Map<ContextType, ContextRoot> mapAttributesWithExtSystem(Map<ContextType, ContextRoot> contextRoots) {
EntityFactory entityFactory = this.context.getEntityFactory()
.orElseThrow(() -> new DataAccessException("ATFX EntityFactory not found!"));
Map<ContextType, ContextRoot> returnVal = contextRoots;
for (ExtSystemAttribute extSystemAttr : extSystemAttrList) {
String compName = getExtAttrComp(extSystemAttr.getName());
String attrName = getExtAttrAttr(extSystemAttr.getName());
if (!Strings.isNullOrEmpty(compName) && !Strings.isNullOrEmpty(attrName)) {
for (ContextRoot root : returnVal.values()) {
Optional<ContextComponent> contextComponentExt = root.getContextComponent(compName);
if (contextComponentExt.isPresent()) {
Value value = contextComponentExt.get().getValues().get(attrName);
if (value != null) {
for (MDMAttribute mdmAttr : extSystemAttr.getAttributes()) {
ContextRoot contextRootMDMAttr = returnVal
.get(ContextType.valueOf(mdmAttr.getComponentType().toUpperCase()));
if (contextRootMDMAttr != null) {
Optional<ContextComponent> contextComponentOptional = contextRootMDMAttr
.getContextComponent(mdmAttr.getComponentName());
ContextComponent contextComponent = null;
if (contextComponentOptional.isPresent()) {
contextComponent = contextComponentOptional.get();
} else {
contextComponent = entityFactory
.createContextComponent(mdmAttr.getComponentName(), contextRootMDMAttr);
}
Value valueMDMAttr = value.getValueType().create(mdmAttr.getAttributeName());
valueMDMAttr.set(value.extract(value.getValueType()));
ODSEntityFactory.extract(contextComponent).getValues()
.put(mdmAttr.getAttributeName(), valueMDMAttr);
}
}
break;
}
}
}
}
}
return returnVal;
}
private void writeValueToMDMAttribute(Map<ContextType, ContextRoot> contextRoots, Value value,
MDMAttribute mdmAttr) {
EntityFactory entityFactory = this.context.getEntityFactory()
.orElseThrow(() -> new DataAccessException("ATFX EntityFactory not found!"));
ContextRoot contextRootMDMAttr = contextRoots
.get(ContextType.valueOf(mdmAttr.getComponentType().toUpperCase()));
if (contextRootMDMAttr != null) {
Optional<ContextComponent> contextComponentOptional = contextRootMDMAttr
.getContextComponent(mdmAttr.getComponentName());
ContextComponent contextComponent = null;
if (contextComponentOptional.isPresent()) {
contextComponent = contextComponentOptional.get();
} else {
contextComponent = entityFactory.createContextComponent(mdmAttr.getComponentName(), contextRootMDMAttr);
}
Value valueMDMAttr = value.getValueType().create(mdmAttr.getAttributeName());
valueMDMAttr.set(value.extract(value.getValueType()));
ODSEntityFactory.extract(contextComponent).getValues().put(mdmAttr.getAttributeName(), valueMDMAttr);
}
}
/**
* {@inheritDoc}
*/
@Override
public List<MeasuredValues> readMeasuredValues(ReadRequest readRequest) throws DataAccessException {
return new ReadRequestHandler(odsModelManager, queryService).execute(readRequest);
}
/**
* {@inheritDoc}
*/
@Override
public Transaction startTransaction() throws DataAccessException {
try {
return new ATFXTransaction(context);
} catch (AoException e) {
throw new DataAccessException("Unable to start transaction due to: " + e.reason, e);
}
}
/**
* Retrives the ASAM paths for the given entities. The ASAM paths are prefixed
* with a servicename, in the form
* <code>corbaloc:[iop|ssliop]:1.2@HOSTNAME:PORT/NameService/MDM.ASAM-ODS/</code>
*
* @returns returns a map with the ASAM paths to the given entities. If a entity
* is not found in the ODS server the entity is not included in the
* result map.
* @throws DataAccessException if links could not be loaded for the given
* entities
* @throws IllegalArgumentException if a the source or typeName of an entity is
* invalid
* @see org.eclipse.mdm.api.base.BaseEntityManager#getLinks(Collection)
*/
@Override
public Map<Entity, String> getLinks(Collection<Entity> entities) throws DataAccessException {
Map<Entity, String> linkMap = new HashMap<>();
ApplicationStructure appStructure;
try {
appStructure = odsModelManager.getAoSession().getApplicationStructure();
} catch (AoException e) {
throw new DataAccessException("Could not load application structure! Reason: " + e.reason, e);
}
String serverRoot = context.getParameters().get(ODSContextFactory.PARAM_NAMESERVICE) + "/"
+ context.getParameters().get(ODSContextFactory.PARAM_SERVICENAME);
Map<String, List<Entity>> entitiesByTypeName = entities.stream().filter(e -> e.getTypeName() != null)
.collect(Collectors.groupingBy(Entity::getTypeName));
for (Map.Entry<String, List<Entity>> entry : entitiesByTypeName.entrySet()) {
ODSEntityType et = (ODSEntityType) odsModelManager.getEntityType(entry.getKey());
List<ElemId> elemIds = entry.getValue().stream()
.map(e -> new ElemId(et.getODSID(), ODSConverter.toODSLong(Long.parseLong(e.getID()))))
.collect(Collectors.toList());
try {
InstanceElement[] instances = appStructure.getInstancesById(elemIds.toArray(new ElemId[0]));
for (InstanceElement ie : instances) {
String id = Long.toString(ODSConverter.fromODSLong(ie.getId()));
String asamPath = serverRoot + ie.getAsamPath();
entry.getValue().stream().filter(e -> e.getID().equals(id)).findFirst()
.ifPresent(e -> linkMap.put(e, asamPath));
}
} catch (AoException e) {
LOGGER.debug("Could not load links for entities: " + entities + ". Reason: " + e.reason, e);
}
}
return linkMap;
}
/**
* {@inheritDoc}
*/
public <T extends Entity> List<T> loadRelatedEntities(Entity entity, String relationName, Class<T> relatedClass) {
ODSEntityType entityType = ((ODSEntityType) context.getODSModelManager().getEntityType(entity));
ElemId elemId = new ElemId(entityType.getODSID(), ODSConverter.toODSID(entity.getID()));
try {
T_LONGLONG[] instanceIds = context.getAoSession().getApplElemAccess().getRelInst(elemId, relationName);
List<String> instanceIDs = Stream.of(instanceIds).map(ODSConverter::fromODSLong).map(Object::toString)
.collect(Collectors.toList());
return entityLoader.loadAll(new Key<>(relatedClass), instanceIDs);
} catch (AoException e) {
throw new DataAccessException("Cannot load related instances: " + e.reason, e);
}
}
/**
* {@inheritDoc}
*/
@Override
public Optional<TemplateTest> loadTemplate(Test test) {
return Optional.of(ODSEntityFactory.extract(test).getMutableStore().get(TemplateTest.class));
}
/**
* {@inheritDoc}
*/
@Override
public Optional<TemplateTestStep> loadTemplate(TestStep testStep) {
return Optional.of(ODSEntityFactory.extract(testStep).getMutableStore().get(TemplateTestStep.class));
}
private static String getExtAttrComp(final String str) {
String returnVal = null;
Matcher matcher = EXTSYSTEMCOMP_REGEX.matcher(str);
if (matcher.matches()) {
returnVal = matcher.group(1);
}
return returnVal;
}
private static String getExtAttrAttr(final String str) {
String returnVal = null;
Matcher matcher = EXTSYSTEMATTR_REGEX.matcher(str);
if (matcher.matches()) {
returnVal = matcher.group(1);
}
return returnVal;
}
}