All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.basyx.aas.restapi.MultiSubmodelProvider Maven / Gradle / Ivy

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