org.eclipse.basyx.aas.restapi.MultiSubmodelProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of basyx.sdk Show documentation
Show all versions of basyx.sdk Show documentation
BaSyx v1.0.0 July 6, 2021.
The newest version!
/*******************************************************************************
* Copyright (C) 2021 the Eclipse BaSyx Authors
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
******************************************************************************/
package org.eclipse.basyx.aas.restapi;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.basyx.aas.metamodel.map.AssetAdministrationShell;
import org.eclipse.basyx.aas.metamodel.map.descriptor.AASDescriptor;
import org.eclipse.basyx.aas.metamodel.map.descriptor.SubmodelDescriptor;
import org.eclipse.basyx.aas.registration.api.IAASRegistry;
import org.eclipse.basyx.aas.restapi.api.IAASAPI;
import org.eclipse.basyx.aas.restapi.api.IAASAPIFactory;
import org.eclipse.basyx.aas.restapi.vab.VABAASAPIFactory;
import org.eclipse.basyx.submodel.metamodel.api.identifier.IIdentifier;
import org.eclipse.basyx.submodel.metamodel.map.Submodel;
import org.eclipse.basyx.submodel.restapi.SubmodelProvider;
import org.eclipse.basyx.submodel.restapi.api.ISubmodelAPI;
import org.eclipse.basyx.submodel.restapi.api.ISubmodelAPIFactory;
import org.eclipse.basyx.submodel.restapi.vab.VABSubmodelAPIFactory;
import org.eclipse.basyx.vab.exception.provider.MalformedRequestException;
import org.eclipse.basyx.vab.exception.provider.ProviderException;
import org.eclipse.basyx.vab.exception.provider.ResourceNotFoundException;
import org.eclipse.basyx.vab.modelprovider.VABPathTools;
import org.eclipse.basyx.vab.modelprovider.api.IModelProvider;
import org.eclipse.basyx.vab.protocol.api.IConnectorFactory;
import org.eclipse.basyx.vab.protocol.http.connector.HTTPConnectorFactory;
/**
* Provider class that implements the AssetAdministrationShellServices
* This provider supports operations on multiple sub models that are selected by
* path
*
* Supported API:
* - getModelPropertyValue
* /aas Returns the Asset Administration Shell
* /aas/submodels Retrieves all Submodels from the current Asset Administration
* Shell
* /aas/submodels/{subModelId} Retrieves a specific Submodel from a specific
* Asset Administration Shell
* /aas/submodels/{subModelId}/properties Retrieves all Properties from the
* current Submodel
* /aas/submodels/{subModelId}/operations Retrieves all Operations from the
* current Submodel
* /aas/submodels/{subModelId}/events Retrieves all Events from the current
* Submodel
* /aas/submodels/{subModelId}/properties/{propertyId} Retrieves a specific
* property from the AAS's Submodel
* /aas/submodels/{subModelId}/operations/{operationId} Retrieves a specific
* Operation from the AAS's Submodel
* /aas/submodels/{subModelId}/events/{eventId} Retrieves a specific event from
* the AAS's submodel
*
* - createValue
* /aas/submodels Adds a new Submodel to an existing Asset Administration Shell
*
* /aas/submodels/{subModelId}/properties Adds a new property to the AAS's
* submodel
* /aas/submodels/{subModelId}/operations Adds a new operation to the AAS's
* submodel
* /aas/submodels/{subModelId}/events Adds a new event to the AAS's submodel
*
* - invokeOperation
* /aas/submodels/{subModelId}/operations/{operationId} Invokes a specific
* operation from the AAS' submodel with a list of input parameters
*
* - deleteValue
* /aas/submodels/{subModelId} Deletes a specific Submodel from a specific Asset
* Administration Shell
* /aas/submodels/{subModelId}/properties/{propertyId} Deletes a specific
* Property from the AAS's Submodel
* /aas/submodels/{subModelId}/operations/{operationId} Deletes a specific
* Operation from the AAS's Submodel
* /aas/submodels/{subModelId}/events/{eventId} Deletes a specific event from
* the AAS's submodel
*
* - setModelPropertyValue
* /aas/submodels/{subModelId}/properties/{propertyId} Sets the value of the
* AAS's Submodel's Property
*
*
* @author kuhn, pschorn
*
*/
public class MultiSubmodelProvider implements IModelProvider {
/**
* Store aas providers
*/
protected AASModelProvider aas_provider = null;
/**
* Store aasId
*/
protected IIdentifier aasId = null;
/**
* Store submodel providers
*/
protected Map submodel_providers = new HashMap<>();
/**
* Store AAS Registry
*/
protected IAASRegistry registry = null;
/**
* Store HTTP Connector
*/
protected IConnectorFactory connectorFactory = null;
/**
* Store AAS API Provider. By default, uses the VAB API Provider
*/
protected IAASAPIFactory aasApiProvider;
/**
* Store Submodel API Provider. By default, uses the VAB Submodel Provider
*/
protected ISubmodelAPIFactory smApiProvider;
/**
* Constructor with empty default aas and default VAB APIs
*/
public MultiSubmodelProvider() {
this.aasApiProvider = new VABAASAPIFactory();
this.smApiProvider = new VABSubmodelAPIFactory();
IAASAPI aasApi = aasApiProvider.getAASApi(new AssetAdministrationShell());
setAssetAdministrationShell(new AASModelProvider(aasApi));
}
/**
* Constructor for using custom APIs
*/
public MultiSubmodelProvider(AASModelProvider contentProvider, IAASAPIFactory aasApiProvider,
ISubmodelAPIFactory smApiProvider) {
this.aasApiProvider = aasApiProvider;
this.smApiProvider = smApiProvider;
setAssetAdministrationShell(contentProvider);
}
/**
* Constructor that accepts an AAS
*/
public MultiSubmodelProvider(AASModelProvider contentProvider) {
this.aasApiProvider = new VABAASAPIFactory();
this.smApiProvider = new VABSubmodelAPIFactory();
// Store content provider
setAssetAdministrationShell(contentProvider);
}
/**
* Constructor that accepts Submodel
*/
public MultiSubmodelProvider(SubmodelProvider contentProvider) {
this();
// Store content provider
addSubmodel(contentProvider);
}
/**
* Constructor that accepts a registry and a connection provider
* @param registry
* @param provider
*/
public MultiSubmodelProvider(IAASRegistry registry, IConnectorFactory provider) {
this();
this.registry = registry;
this.connectorFactory = provider;
}
/**
* Constructor that accepts a registry, a connection provider and API providers
*/
public MultiSubmodelProvider(AASModelProvider contentProvider, IAASRegistry registry,
IConnectorFactory connectorFactory, ISubmodelAPIFactory smApiProvider, IAASAPIFactory aasApiProvider) {
this(contentProvider, aasApiProvider, smApiProvider);
this.registry = registry;
this.connectorFactory = connectorFactory;
}
/**
* Constructor that accepts a aas provider, a registry and a connection provider
*
* @param contentProvider
* @param registry
* @param provider
*/
public MultiSubmodelProvider(AASModelProvider contentProvider, IAASRegistry registry, HTTPConnectorFactory provider) {
this(contentProvider);
this.registry = registry;
this.connectorFactory = provider;
}
/**
* Set an AAS for this provider
*
* @param elementId
* Element ID
* @param modelContentProvider
* Model content provider
*/
@SuppressWarnings("unchecked")
public void setAssetAdministrationShell(AASModelProvider modelContentProvider) {
// Add model provider
aas_provider = modelContentProvider;
aasId = AssetAdministrationShell.createAsFacade((Map) modelContentProvider.getValue("")).getIdentification();
}
@SuppressWarnings("unchecked")
public void addSubmodel(SubmodelProvider modelContentProvider) {
Submodel sm = Submodel.createAsFacade((Map) modelContentProvider.getValue("/"));
addSubmodel(sm, modelContentProvider);
}
@SuppressWarnings("unchecked")
private void createSubmodel(Object newSM) throws ProviderException {
// Adds a new submodel to the registered AAS
Submodel sm = Submodel.createAsFacade((Map) newSM);
ISubmodelAPI smApi = smApiProvider.getSubmodelAPI(sm);
addSubmodel(sm, new SubmodelProvider(smApi));
}
private void addSubmodel(Submodel sm, SubmodelProvider modelContentProvider) {
String smIdShort = sm.getIdShort();
submodel_providers.put(smIdShort, modelContentProvider);
aas_provider.createValue("/submodels", sm);
}
/**
* Remove a provider
*
* @param elementId
* Element ID
*/
public void removeProvider(String elementId) {
// Remove model provider
submodel_providers.remove(elementId);
}
/**
* Get the value of an element
*/
@Override
public Object getValue(String path) throws ProviderException {
VABPathTools.checkPathForNull(path);
path = VABPathTools.stripSlashes(path);
String[] pathElements = VABPathTools.splitPath(path);
if (pathElements.length > 0 && pathElements[0].equals("aas")) {
if (pathElements.length == 1) {
return aas_provider.getValue("");
}
if (pathElements[1].equals(AssetAdministrationShell.SUBMODELS)) {
if (pathElements.length == 2) {
return retrieveSubmodels();
} else {
IModelProvider provider = submodel_providers.get(pathElements[2]);
if (provider == null) {
// Get a model provider for the submodel in the registry
provider = getModelProvider(pathElements[2]);
}
// - Retrieve submodel or property value
return provider.getValue(VABPathTools.buildPath(pathElements, 4));
}
} else {
// Handle access to AAS
return aas_provider.getValue(VABPathTools.buildPath(pathElements, 1));
}
} else {
return new MalformedRequestException("The request " + path + " is not allowed for this endpoint");
}
}
/**
* Retrieves all submodels of the AAS. If there's a registry, remote Submodels
* will also be retrieved.
*
* @return
* @throws ProviderException
*/
@SuppressWarnings("unchecked")
private Object retrieveSubmodels() throws ProviderException {
// Make a list and return all local submodels
Collection submodels = new HashSet<>();
for (IModelProvider submodel : submodel_providers.values()) {
submodels.add(Submodel.createAsFacade((Map) submodel.getValue("")));
}
// Check for remote submodels
if (registry != null) {
AASDescriptor desc = registry.lookupAAS(aasId);
// Get the address of the AAS e.g. http://localhost:8080
// This address should be equal to the address of this server
String aasEndpoint = desc.getFirstEndpoint();
String aasServerURL = getServerURL(aasEndpoint);
List localIds = submodels.stream().map(sm -> sm.getIdentification().getId()).collect(Collectors.toList());
List missingIds = desc.getSubmodelDescriptors().stream().map(d -> d.getIdentifier()).
filter(id -> !localIds.contains(id.getId())).collect(Collectors.toList());
if(!missingIds.isEmpty()) {
List missingEndpoints = missingIds.stream().map(id -> desc.getSubmodelDescriptorFromIdentifierId(id.getId()))
.map(smDesc -> smDesc.getFirstEndpoint()).collect(Collectors.toList());
// Check if any of the missing Submodels have the same address as the AAS.
// This would mean, that the Submodel should be present on the same
// server of the AAS but is not
// If this error would not be caught here an endless loop would develop
// as the registry would be asked for this Submodel and then it would be requested
// from this server again, which would ask the registry about it again
// Such a situation might originate from a deleted but not unregistered Submodel
// or from a manually registered but never pushed Submodel
for(String missingEndpoint: missingEndpoints) {
if(getServerURL(missingEndpoint).equals(aasServerURL)) {
throw new ResourceNotFoundException("The Submodel at Endpoint '" + missingEndpoint +
"' does not exist on this server. It seems to be registered but not actually present.");
}
}
List remoteSms = missingEndpoints.stream().map(endpoint -> connectorFactory.getConnector(endpoint)).
map(p -> (Map) p.getValue("")).map(m -> Submodel.createAsFacade(m)).collect(Collectors.toList());
submodels.addAll(remoteSms);
}
}
return submodels;
}
/**
* Change a model property value
*/
@Override
public void setValue(String path, Object newValue) throws ProviderException {
VABPathTools.checkPathForNull(path);
path = VABPathTools.stripSlashes(path);
// Split path
String[] pathElements = VABPathTools.splitPath(path);
String propertyPath = VABPathTools.buildPath(pathElements, 3);
// - Ignore first 2 elements, as it is "/aas/submodels" --> 'aas','submodels'
if (path.equals("aas")) {
createAssetAdministrationShell(newValue);
} else if (!path.startsWith("aas/submodels")) {
throw new MalformedRequestException("Access to MultiSubmodelProvider always has to start with \"aas/submodels\", was " + path);
} else if (propertyPath.isEmpty()) {
createSubmodel(newValue);
} else {
IModelProvider provider;
if (isSubmodelLocal(pathElements[2])) {
provider = submodel_providers.get(pathElements[2]);
} else {
// Get a model provider for the submodel in the registry
provider = getModelProvider(pathElements[2]);
}
provider.setValue(propertyPath, newValue);
}
}
@Override
public void createValue(String path, Object newValue) throws ProviderException {
throw new MalformedRequestException("Create is not supported by VABMultiSubmodelProvider. Path was: " + path);
}
@SuppressWarnings("unchecked")
private void createAssetAdministrationShell(Object newAAS) {
Map aas = (Map) newAAS;
AssetAdministrationShell shell = AssetAdministrationShell.createAsFacade(aas);
IAASAPI aasApi = aasApiProvider.getAASApi(shell);
aas_provider = new AASModelProvider(aasApi);
}
@SuppressWarnings("unchecked")
@Override
public void deleteValue(String path) throws ProviderException {
VABPathTools.checkPathForNull(path);
path = VABPathTools.stripSlashes(path);
String[] pathElements = VABPathTools.splitPath(path);
String propertyPath = VABPathTools.buildPath(pathElements, 3);
// - Ignore first 2 elements, as it is "/aas/submodels" --> 'aas','submodels'
if (pathElements.length == 3) {
// Delete Submodel from registered AAS
String smIdShort = pathElements[2];
if (!isSubmodelLocal(smIdShort)) {
return;
}
// Delete submodel reference from aas
// TODO: This is a hack until the API is further clarified
Submodel sm = Submodel.createAsFacade((Map) submodel_providers.get(smIdShort).getValue("/"));
aas_provider.deleteValue("aas/submodels/" + sm.getIdentification().getId());
// Remove submodel provider
submodel_providers.remove(smIdShort);
} else if (propertyPath.length() > 0) {
IModelProvider provider;
if (isSubmodelLocal(pathElements[2])) {
provider = submodel_providers.get(pathElements[2]);
} else {
// Get a model provider for the submodel in the registry
provider = getModelProvider(pathElements[2]);
}
provider.deleteValue(propertyPath);
}
}
@Override
public void deleteValue(String path, Object obj) throws ProviderException {
throw new MalformedRequestException("DeleteValue with a parameter is not supported. Path was: " + path);
}
@Override
public Object invokeOperation(String path, Object... parameter) throws ProviderException {
VABPathTools.checkPathForNull(path);
path = VABPathTools.stripSlashes(path);
String[] pathElements = VABPathTools.splitPath(path);
String operationPath = VABPathTools.buildPath(pathElements, 3);
// - Ignore first 2 elements, as it is "/aas/submodels" --> 'aas','submodels'
// - Invoke provider and return result
IModelProvider provider;
if (isSubmodelLocal(pathElements[2])) {
provider = submodel_providers.get(pathElements[2]);
} else {
// Get a model provider for the submodel in the registry
provider = getModelProvider(pathElements[2]);
}
return provider.invokeOperation(operationPath, parameter);
}
/**
* Check whether the given submodel exists in submodel provider
* @param key to search the submodel
* @return boolean true/false
*/
private boolean isSubmodelLocal(String submodelId) {
return submodel_providers.containsKey(submodelId);
}
/**
* Check whether a registry exists
* @return boolean true/false
*/
private boolean doesRegistryExist() {
return this.registry != null;
}
/**
* Get submodel descriptor from the registry
* @param submodelId to search the submodel
* @return a specifi submodel descriptor
*/
private SubmodelDescriptor getSubmodelDescriptorFromRegistry(String submodelIdShort) {
AASDescriptor aasDescriptor = registry.lookupAAS(aasId);
SubmodelDescriptor desc = aasDescriptor.getSubmodelDescriptorFromIdShort(submodelIdShort);
if(desc == null) {
throw new ResourceNotFoundException("Could not resolve Submodel with idShort " + submodelIdShort + " for AAS " + aasId);
}
return desc;
}
/**
* Get a model provider from a submodel descriptor
* @param submodelDescriptor
* @return a model provider
*/
private IModelProvider getModelProvider(SubmodelDescriptor submodelDescriptor) {
String endpoint = submodelDescriptor.getFirstEndpoint();
// Remove "/submodel" since it will be readded later
endpoint = endpoint.substring(0, endpoint.length() - SubmodelProvider.SUBMODEL.length() - 1);
return connectorFactory.getConnector(endpoint);
}
/**
* Get a model provider from a submodel id
* @param submodelId to select a specific submodel
* @throws ResourceNotFoundException if no registry is found
* @return a model provider
*/
private IModelProvider getModelProvider(String submodelId) {
if (!doesRegistryExist()) {
throw new ResourceNotFoundException("Submodel with id " + submodelId + " cannot be resolved locally, but no registry is passed");
}
SubmodelDescriptor submodelDescriptor = getSubmodelDescriptorFromRegistry(submodelId);
return getModelProvider(submodelDescriptor);
}
/**
* Gets the server URL of a given endpoint.
* e.g. http://localhost:1234/x/y/z/aas/submodels/Sm1IdShort would return
* http://localhost:1234/x/y/z
*
* @param endpoint
* @return the server URL part of the given endpoint
*/
public static String getServerURL(String endpoint) {
int endServerURL = endpoint.indexOf("/aas");
// if indexOf returned -1 ("/aas" not present in String)
// return the whole given path
if(endServerURL < 0) {
return endpoint;
}
return endpoint.substring(0, endServerURL);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy