blob: 50f9917b1e0c3a8bf713e42342829fa67e3a9e78 [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.odsadapter.query;
import static java.util.stream.Collectors.groupingBy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Stream;
import org.asam.ods.AIDName;
import org.asam.ods.AggrFunc;
import org.asam.ods.AoException;
import org.asam.ods.AoSession;
import org.asam.ods.ApplElem;
import org.asam.ods.ApplElemAccess;
import org.asam.ods.ApplRel;
import org.asam.ods.ApplicationStructureValue;
import org.asam.ods.ElemResultSetExt;
import org.asam.ods.EnumerationAttributeStructure;
import org.asam.ods.EnumerationItemStructure;
import org.asam.ods.EnumerationStructure;
import org.asam.ods.JoinDef;
import org.asam.ods.QueryStructureExt;
import org.asam.ods.SelAIDNameUnitId;
import org.asam.ods.SelItem;
import org.asam.ods.SelOrder;
import org.asam.ods.T_LONGLONG;
import org.eclipse.mdm.api.base.adapter.EntityType;
import org.eclipse.mdm.api.base.adapter.ModelManager;
import org.eclipse.mdm.api.base.adapter.Relation;
import org.eclipse.mdm.api.base.model.ContextType;
import org.eclipse.mdm.api.base.model.Entity;
import org.eclipse.mdm.api.base.model.EnumRegistry;
import org.eclipse.mdm.api.base.model.Enumeration;
import org.eclipse.mdm.api.base.model.Unit;
import org.eclipse.mdm.api.odsadapter.lookup.config.DefaultEntityConfigRepositoryLoader;
import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig;
import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig.Key;
import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfigRepository;
import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfigRepositoryLoader;
import org.eclipse.mdm.api.odsadapter.utils.ODSConverter;
import org.eclipse.mdm.api.odsadapter.utils.ODSEnum;
import org.eclipse.mdm.api.odsadapter.utils.ODSEnumerations;
import org.omg.CORBA.ORB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ODS implementation of the {@link ModelManager} interface.
*
* @since 1.0.0
* @author Viktor Stoehr, Gigatronik Ingolstadt GmbH
*/
public class ODSModelManager implements ModelManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ODSModelManager.class);
private final Map<String, EntityType> entityTypesByName = new HashMap<>();
private final ORB orb;
private final Lock write;
private final Lock read;
private EntityConfigRepository entityConfigRepository;
private EntityConfigRepositoryLoader entityConfigRepositoryLoader;
private ApplElemAccess applElemAccess;
private AoSession aoSession;
/**
* Constructor.
*
* @param orb Used to activate CORBA service objects.
* @param aoSession The underlying ODS session.
* @throws AoException Thrown on errors.
*/
public ODSModelManager(ORB orb, AoSession aoSession) throws AoException {
this(orb, aoSession, new DefaultEntityConfigRepositoryLoader());
}
/**
* Constructor.
*
* @param orb Used to activate CORBA service objects.
* @param aoSession The underlying ODS session.
* @throws AoException Thrown on errors.
*/
public ODSModelManager(ORB orb, AoSession aoSession, EntityConfigRepositoryLoader entityConfigRepositoryLoader)
throws AoException {
this.aoSession = aoSession;
this.orb = orb;
applElemAccess = aoSession.getApplElemAccess();
this.entityConfigRepositoryLoader = entityConfigRepositoryLoader;
// setup locks
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
write = reentrantReadWriteLock.writeLock();
read = reentrantReadWriteLock.readLock();
initialize();
}
/**
* Returns the {@link ORB}.
*
* @return The {@code ORB} is returned.
*/
public ORB getORB() {
return orb;
}
/**
* Returns the non root {@link EntityConfig} for given {@link Key}.
*
* @param <T> The concrete entity type.
* @param key Used as identifier.
* @return The non root {@code EntityConfig} is returned.
*/
public <T extends Entity> EntityConfig<T> findEntityConfig(Key<T> key) {
read.lock();
try {
return entityConfigRepository.find(key);
} finally {
read.unlock();
}
}
/**
* Returns the root {@link EntityConfig} for given {@link Key}.
*
* @param <T> The concrete entity type.
* @param key Used as identifier.
* @return The root {@code EntityConfig} is returned.
*/
public <T extends Entity> EntityConfig<T> getEntityConfig(Key<T> key) {
read.lock();
try {
return entityConfigRepository.findRoot(key);
} finally {
read.unlock();
}
}
/**
* Returns the {@link EntityConfig} associated with given {@link EntityType}.
*
* @param entityType Used as identifier.
* @return The {@code EntityConfig} is returned.
*/
public EntityConfig<?> getEntityConfig(EntityType entityType) {
read.lock();
try {
return entityConfigRepository.find(entityType);
} finally {
read.unlock();
}
}
/**
* {@inheritDoc}
*/
@Override
public List<EntityType> listEntityTypes() {
read.lock();
try {
return Collections.unmodifiableList(new ArrayList<>(entityTypesByName.values()));
} finally {
read.unlock();
}
}
/**
* {@inheritDoc}
*/
@Override
public EntityType getEntityType(Class<? extends Entity> entityClass) {
read.lock();
try {
return getEntityConfig(new Key<>(entityClass)).getEntityType();
} finally {
read.unlock();
}
}
/**
* {@inheritDoc}
*/
@Override
public EntityType getEntityType(Class<? extends Entity> entityClass, ContextType contextType) {
read.lock();
try {
return getEntityConfig(new Key<>(entityClass, contextType)).getEntityType();
} finally {
read.unlock();
}
}
/**
* {@inheritDoc}
*/
@Override
public EntityType getEntityType(String name) {
read.lock();
try {
EntityType entityType = entityTypesByName.get(name);
if (entityType == null) {
throw new IllegalArgumentException("Entity with name '" + name + "' not found.");
}
return entityType;
} finally {
read.unlock();
}
}
@Override
public EntityType getEntityTypeById(String id) {
Optional<EntityType> entityType = listEntityTypes().stream().filter(et -> et.getId().equals(id)).findFirst();
if (!entityType.isPresent()) {
throw new IllegalArgumentException("Entity with id '" + id + "' not found.");
}
return entityType.get();
}
/**
* Returns the {@link AoSession} of this model manager.
*
* @return The {@code AoSession} is returned.
*/
public AoSession getAoSession() {
write.lock();
try {
return aoSession;
} finally {
write.unlock();
}
}
/**
* Returns the {@link ApplElemAccess} of this model manager.
*
* @return The {@code ApplElemAccess} is returned.
*/
public ApplElemAccess getApplElemAccess() {
read.lock();
try {
return applElemAccess;
} finally {
read.unlock();
}
}
/**
* Closes the ODS connection.
*
* @throws AoException Thrown on errors.
*/
public void close() throws AoException {
read.lock();
try {
applElemAccess._release();
aoSession.close();
} finally {
read.unlock();
aoSession._release();
}
}
/**
* Reloads the complete session context.
*/
public void reloadApplicationModel() {
write.lock();
AoSession aoSessionOld = aoSession;
ApplElemAccess applElemAccessOld = applElemAccess;
try {
entityTypesByName.clear();
aoSession = aoSession.createCoSession();
applElemAccess = aoSession.getApplElemAccess();
initialize();
} catch (AoException e) {
LOGGER.error("Unable to reload the application model due to: " + e.reason, e);
} finally {
write.unlock();
}
try {
applElemAccessOld._release();
aoSessionOld.close();
} catch (AoException e) {
LOGGER.debug("Unable to close replaced session due to: " + e.reason, e);
} finally {
aoSessionOld._release();
}
}
/**
* Initializes this model manager by caching the application model and loading
* the {@link EntityConfig}s.
*
* @throws AoException Thrown on errors.
*/
private void initialize() throws AoException {
loadApplicationModel();
entityConfigRepository = entityConfigRepositoryLoader.loadEntityConfigurations(this);
}
/**
* Caches the whole application model as provided by the ODS session.
*
* @throws AoException Thrown on errors.
*/
private void loadApplicationModel() throws AoException {
LOGGER.debug("Reading the application model...");
Map<String, Map<String, Enumeration<?>>> enumClassMap = loadODSEnumerations();
long start = System.currentTimeMillis();
ApplicationStructureValue applicationStructureValue = aoSession.getApplicationStructureValue();
Map<String, String> units = getUnitMapping(applicationStructureValue.applElems);
// create entity types (incl. attributes)
Map<String, ODSEntityType> entityTypesByID = new HashMap<>();
String sourceName = aoSession.getName();
for (ApplElem applElem : applicationStructureValue.applElems) {
String odsID = Long.toString(ODSConverter.fromODSLong(applElem.aid));
Map<String, Enumeration<?>> entityEnumMap = enumClassMap.getOrDefault(odsID, new HashMap<>());
ODSEntityType entityType = new ODSEntityType(sourceName, applElem, units, entityEnumMap);
entityTypesByName.put(applElem.aeName, entityType);
entityTypesByID.put(odsID, entityType);
}
// create relations
List<Relation> relations = new ArrayList<>();
for (ApplRel applRel : applicationStructureValue.applRels) {
EntityType source = entityTypesByID.get(Long.toString(ODSConverter.fromODSLong(applRel.elem1)));
EntityType target = entityTypesByID.get(Long.toString(ODSConverter.fromODSLong(applRel.elem2)));
relations.add(new ODSRelation(applRel, source, target));
}
// assign relations to their source entity types
relations.stream().collect(groupingBy(Relation::getSource))
.forEach((e, r) -> ((ODSEntityType) e).setRelations(r));
long stop = System.currentTimeMillis();
LOGGER.debug("{} entity types with {} relations found in {} ms.", entityTypesByName.size(), relations.size(),
stop - start);
}
private Map<String, Map<String, Enumeration<?>>> loadODSEnumerations() throws AoException {
LOGGER.debug("Loading ODS enumerations...");
long t1 = System.currentTimeMillis();
// enumeration mappings (aeID -> (aaName -> enumClass))
Map<String, Map<String, Enumeration<?>>> enumClassMap = new HashMap<>();
EnumRegistry er = EnumRegistry.getInstance();
Map<String, EnumerationItemStructure[]> map = new HashMap<>();
for (EnumerationStructure es : aoSession.getEnumerationStructure()) {
map.put(es.enumName, es.items);
}
for (EnumerationAttributeStructure eas : aoSession.getEnumerationAttributes()) {
// make sure the enumeration is found
if (ODSEnumerations.getEnumObj(eas.enumName) == null) {
Enumeration<ODSEnum> enumdyn = new Enumeration<>(ODSEnum.class, eas.enumName);
for (EnumerationItemStructure enumItemStruct : map.get(eas.enumName)) {
LOGGER.trace("{}:{}:{}", eas.enumName, enumItemStruct.itemName, enumItemStruct.index);
enumdyn.addValue(new ODSEnum(enumItemStruct.itemName, enumItemStruct.index));
}
er.add(eas.enumName, enumdyn);
}
enumClassMap.computeIfAbsent(Long.toString(ODSConverter.fromODSLong(eas.aid)), k -> new HashMap<>())
.put(eas.aaName, ODSEnumerations.getEnumObj(eas.enumName));
}
LOGGER.debug("Loading enumerations took {}ms", System.currentTimeMillis() - t1);
return enumClassMap;
}
/**
* Loads all available {@link Unit} names mapped by their instance IDs.
*
* @param applElems The application element meta data instances.
* @return The unit names mapped by the corresponding instance IDs.
* @throws AoException Thrown if unable to load the unit mappings.
*/
private Map<String, String> getUnitMapping(ApplElem[] applElems) throws AoException {
ApplElem unitElem = Stream.of(applElems).filter(ae -> ae.beName.equals("AoUnit")).findAny()
.orElseThrow(() -> new IllegalStateException("Application element 'Unit' is not defined."));
QueryStructureExt qse = new QueryStructureExt();
qse.anuSeq = new SelAIDNameUnitId[] {
new SelAIDNameUnitId(new AIDName(unitElem.aid, "Id"), new T_LONGLONG(), AggrFunc.NONE),
new SelAIDNameUnitId(new AIDName(unitElem.aid, "Name"), new T_LONGLONG(), AggrFunc.NONE) };
qse.condSeq = new SelItem[0];
qse.groupBy = new AIDName[0];
qse.joinSeq = new JoinDef[0];
qse.orderBy = new SelOrder[0];
Map<String, String> units = new HashMap<>();
ElemResultSetExt unitResultSetExt = getApplElemAccess().getInstancesExt(qse, 0)[0].firstElems[0];
for (int i = 0; i < unitResultSetExt.values[0].value.flag.length; i++) {
String unitID = Long
.toString(ODSConverter.fromODSLong(unitResultSetExt.values[0].value.u.longlongVal()[i]));
String unitName = unitResultSetExt.values[1].value.u.stringVal()[i];
units.put(unitID, unitName);
}
return units;
}
}