Skip to content

Commit

Permalink
Implement Upload Endpoint for AASEnvironment (eclipse-basyx#218)
Browse files Browse the repository at this point in the history
* Extract logic of Environment loading from PreconfigurationLoader

* Implement AASXUpload Endpoint

* Further qualify endpoint and enable support to any valid Environment
Type

* Rename SerializationApiController to AasEnvironmentApiHTTPController

* Move loading env loading loop to Preconfiguration

* Move environmentLoader helper classes to the same package

* Implement tests for AasEnvironmentLoader based on tests from Preconfig

* Improve consistency of how EnvPreconfiguration is constructed

* Adds minor changes during review

Signed-off-by: Jannis Jung <[email protected]>

---------

Signed-off-by: Jannis Jung <[email protected]>
Co-authored-by: Jannis Jung <[email protected]>
  • Loading branch information
mateusmolina-iese and jannisjung authored Feb 23, 2024
1 parent aebde5b commit 966dd58
Show file tree
Hide file tree
Showing 20 changed files with 1,415 additions and 405 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*******************************************************************************
* Copyright (C) 2024 the Eclipse BaSyx Authors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* SPDX-License-Identifier: MIT
******************************************************************************/

package org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.apache.commons.io.FilenameUtils;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.InMemoryFile;
import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell;
import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription;
import org.eclipse.digitaltwin.aas4j.v3.model.Environment;
import org.eclipse.digitaltwin.aas4j.v3.model.Submodel;
import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement;
import org.eclipse.digitaltwin.basyx.aasenvironment.FileElementPathCollector;
import org.eclipse.digitaltwin.basyx.aasenvironment.IdShortPathBuilder;
import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.IdentifiableUploader.DelegatingIdentifiableRepository;
import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.IdentifiableUploader.IdentifiableRepository;
import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository;
import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository;
import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Loader for AAS Environment
*
* @author fried, mateusmolina, despen, witt, jungjan, danish
*
*/
public class AasEnvironmentLoader {
private Logger logger = LoggerFactory.getLogger(AasEnvironmentLoader.class);
private IndentifiableAssertion checker = new IndentifiableAssertion();

private AasRepository aasRepository;
private SubmodelRepository submodelRepository;
private ConceptDescriptionRepository conceptDescriptionRepository;

public AasEnvironmentLoader(AasRepository aasRepository, SubmodelRepository submodelRepository, ConceptDescriptionRepository conceptDescriptionRepository) {
this.aasRepository = aasRepository;
this.submodelRepository = submodelRepository;
this.conceptDescriptionRepository = conceptDescriptionRepository;
}

public void loadEnvironment(CompleteEnvironment completeEnvironment) {
Environment environment = completeEnvironment.getEnvironment();

if (environment == null)
return;

checker.assertNoDuplicateIds(environment);

createShellsOnRepositoryFromEnvironment(environment);
createSubmodelsOnRepositoryFromEnvironment(environment, completeEnvironment.getRelatedFiles());
createConceptDescriptionsOnRepositoryFromEnvironment(environment);
}

private void createConceptDescriptionsOnRepositoryFromEnvironment(Environment environment) {
IdentifiableRepository<ConceptDescription> repo = new DelegatingIdentifiableRepository<ConceptDescription>(conceptDescriptionRepository::getConceptDescription, conceptDescriptionRepository::updateConceptDescription,
conceptDescriptionRepository::createConceptDescription);
IdentifiableUploader<ConceptDescription> uploader = new IdentifiableUploader<ConceptDescription>(repo);
for (ConceptDescription conceptDescription : environment.getConceptDescriptions()) {
boolean success = uploader.upload(conceptDescription);
logSuccessConceptDescription(conceptDescription.getId(), success);
}
}

private void createSubmodelsOnRepositoryFromEnvironment(Environment environment, List<InMemoryFile> relatedFiles) {
List<Submodel> submodels = environment.getSubmodels();

createSubmodelsOnRepository(submodels);

if (relatedFiles == null || relatedFiles.isEmpty())
return;

for (Submodel submodel : submodels) {
List<List<SubmodelElement>> idShortElementPathsOfAllFileSMEs = new FileElementPathCollector(submodel).collect();

idShortElementPathsOfAllFileSMEs.stream().forEach(fileSMEIdShortPath -> setFileToFileElement(submodel.getId(), fileSMEIdShortPath, relatedFiles));
}
}

private void setFileToFileElement(String submodelId, List<SubmodelElement> fileSMEIdShortPathElements, List<InMemoryFile> relatedFiles) {
String fileSMEIdShortPath = new IdShortPathBuilder(new ArrayList<>(fileSMEIdShortPathElements)).build();

org.eclipse.digitaltwin.aas4j.v3.model.File fileSME = (org.eclipse.digitaltwin.aas4j.v3.model.File) submodelRepository.getSubmodelElement(submodelId, fileSMEIdShortPath);

InMemoryFile inMemoryFile = getAssociatedInMemoryFile(relatedFiles, fileSME.getValue());

if (inMemoryFile == null) {
logger.info("Unable to set file to the SubmodelElement File with IdShortPath '{}' because it does not exist in the AASX file.", fileSMEIdShortPath);

return;
}

submodelRepository.setFileValue(submodelId, fileSMEIdShortPath, getFileName(inMemoryFile.getPath()), new ByteArrayInputStream(inMemoryFile.getFileContent()));
}

private String getFileName(String path) {
return FilenameUtils.getName(path);
}

private InMemoryFile getAssociatedInMemoryFile(List<InMemoryFile> relatedFiles, String value) {

Optional<InMemoryFile> inMemoryFile = relatedFiles.stream().filter(file -> file.getPath().equals(value)).findAny();

if (inMemoryFile.isEmpty())
return null;

return inMemoryFile.get();
}

private void createShellsOnRepositoryFromEnvironment(Environment environment) {
IdentifiableRepository<AssetAdministrationShell> repo = new DelegatingIdentifiableRepository<AssetAdministrationShell>(aasRepository::getAas, aasRepository::updateAas, aasRepository::createAas);
IdentifiableUploader<AssetAdministrationShell> uploader = new IdentifiableUploader<>(repo);
for (AssetAdministrationShell shell : environment.getAssetAdministrationShells()) {
boolean success = uploader.upload(shell);
logSuccess("shell", shell.getId(), success);
}
}

private void createSubmodelsOnRepository(List<Submodel> submodels) {
IdentifiableRepository<Submodel> repo = new DelegatingIdentifiableRepository<Submodel>(submodelRepository::getSubmodel, submodelRepository::updateSubmodel, submodelRepository::createSubmodel);
IdentifiableUploader<Submodel> uploader = new IdentifiableUploader<>(repo);
for (Submodel submodel : submodels) {
boolean success = uploader.upload(submodel);
logSuccess("submodel", submodel.getId(), success);
}
}

private void logSuccess(String resourceName, String id, boolean success) {
if (success) {
logger.info("Uploading " + resourceName + " " + id + " was successful!");
} else {
logger.warn("Uploading " + resourceName + " " + id + " was not successful!");
}
}

private void logSuccessConceptDescription(String conceptDescriptionId, boolean success) {
if (!success) {
logger.warn("Colliding Ids detected for ConceptDescription: " + conceptDescriptionId + ". If they are not identical, this is an error. Please note that the already existing ConceptDescription was not updated.");
} else {
logSuccess("conceptDescription", conceptDescriptionId, success);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*******************************************************************************
* Copyright (C) 2024 the Eclipse BaSyx Authors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* SPDX-License-Identifier: MIT
******************************************************************************/

package org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.AASXDeserializer;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.InMemoryFile;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonDeserializer;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.xml.XmlDeserializer;
import org.eclipse.digitaltwin.aas4j.v3.model.Environment;

/**
* Represents an environment and its relatedFiles
*
* @author mateusmolina
*
*/
public class CompleteEnvironment {
private final Environment environment;
private final List<InMemoryFile> relatedFiles;

public enum EnvironmentType {
AASX, JSON, XML;

public static EnvironmentType getFromMimeType(String mimeType) {
switch (mimeType) {
case "application/asset-administration-shell-package":
return AASX;
case "application/json":
return JSON;
case "application/xml":
return XML;
case "text/xml":
return XML;
default:
return null;
}
}

public static EnvironmentType getFromFilePath(String filePath) {
if (filePath.endsWith(".json"))
return JSON;
if (filePath.endsWith(".aasx"))
return AASX;
if (filePath.endsWith(".xml"))
return XML;
return null;
}

}

public CompleteEnvironment(Environment environment, List<InMemoryFile> relatedFiles) {
this.environment = environment;
this.relatedFiles = relatedFiles;
}

public Environment getEnvironment() {
return environment;
}

public List<InMemoryFile> getRelatedFiles() {
return relatedFiles;
}

public static CompleteEnvironment fromFile(File file) throws DeserializationException, InvalidFormatException, IOException {
return fromInputStream(new FileInputStream(file), EnvironmentType.getFromFilePath(file.getPath()));
}

public static CompleteEnvironment fromInputStream(InputStream inputStream, EnvironmentType envType) throws DeserializationException, InvalidFormatException, IOException {
Environment environment = null;
List<InMemoryFile> relatedFiles = null;

if(envType == EnvironmentType.JSON) {
JsonDeserializer deserializer = new JsonDeserializer();
environment = deserializer.read(inputStream, Environment.class);
}
if(envType == EnvironmentType.XML) {
XmlDeserializer deserializer = new XmlDeserializer();
environment = deserializer.read(inputStream);
}
if(envType == EnvironmentType.AASX) {
AASXDeserializer deserializer = new AASXDeserializer(inputStream);
relatedFiles = deserializer.getRelatedFiles();
environment = deserializer.read();
}

return new CompleteEnvironment(environment, relatedFiles);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
* SPDX-License-Identifier: MIT
******************************************************************************/

package org.eclipse.digitaltwin.basyx.aasenvironment.preconfiguration;
package org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader;

import java.util.Objects;
import java.util.Optional;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
*
* SPDX-License-Identifier: MIT
******************************************************************************/
package org.eclipse.digitaltwin.basyx.aasenvironment.preconfiguration;
package org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader;

import java.util.HashSet;
import java.util.List;
Expand Down
Loading

0 comments on commit 966dd58

Please sign in to comment.