| /******************************************************************************** |
| * 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; |
| |
| 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.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| 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.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.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.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 Logger LOGGER = LoggerFactory.getLogger(ATFXEntityManager.class); |
| |
| private final ATFXContext context; |
| private final ODSModelManager odsModelManager; |
| private final QueryService queryService; |
| private final EntityLoader entityLoader; |
| |
| /** |
| * Constructor. |
| * |
| * @param context The {@link ODSContext}. |
| */ |
| public ATFXEntityManager(ATFXContext context) { |
| this.context = context; |
| this.odsModelManager = context.getODSModelManager(); |
| this.queryService = context.getQueryService() |
| .orElseThrow(() -> new ServiceNotProvidedException(QueryService.class)); |
| 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 { |
| T_LONGLONG[] contextRootIds = odsModelManager.getApplElemAccess().getRelInst(elemId, |
| contextDescribableEntityType.getRelation(entityType).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); |
| } |
| } |
| return contextRoots; |
| } |
| |
| /** |
| * {@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)); |
| } |
| } |