Skip to content

Commit

Permalink
Merge branch 'main' into feat/add-self-description-information
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-schmidt committed Dec 17, 2023
2 parents ecb9335 + 8481512 commit 7642297
Show file tree
Hide file tree
Showing 24 changed files with 1,120 additions and 109 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ jobs:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@0280eb7de5ad3fb0deb50017b8ce842980b4789a
uses: gradle/gradle-build-action@f95e9c74599bc49122b76ecfe1306f0034f87266
with:
arguments: clean build
14 changes: 5 additions & 9 deletions edc-extension4aas/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ dependencies {
implementation("jakarta.ws.rs:jakarta.ws.rs-api:${rsApi}")

testImplementation("$group:junit:$edcVersion")
testImplementation("org.glassfish.jersey.core:jersey-common:3.1.4")
testImplementation("org.glassfish.jersey.core:jersey-common:3.1.5")
testImplementation("org.mockito:mockito-core:${mockitoVersion}")
testImplementation("org.mock-server:mockserver-junit-jupiter:${mockserverVersion}")
testImplementation("org.mock-server:mockserver-netty:${mockserverVersion}")
Expand All @@ -40,14 +40,10 @@ dependencies {
repositories {
mavenCentral()
}

tasks.test {
useJUnitPlatform()
}

tasks.jacocoTestReport {
dependsOn(tasks.test) // tests are required to run before generating the report
}
tasks.compileJava {options.encoding = "UTF-8"}
tasks.compileTestJava {options.encoding = "UTF-8"}
tasks.test {useJUnitPlatform()}
tasks.jacocoTestReport {dependsOn(tasks.test)}

// FA³ST dependency needs the following
configurations.all {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,31 @@
*/
package de.fraunhofer.iosb.app.aas;

import static java.lang.String.format;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

import org.eclipse.edc.spi.EdcException;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import de.fraunhofer.iosb.app.Logger;
import de.fraunhofer.iosb.app.model.aas.*;
import de.fraunhofer.iosb.app.model.aas.CustomAssetAdministrationShell;
import de.fraunhofer.iosb.app.model.aas.CustomAssetAdministrationShellEnvironment;
import de.fraunhofer.iosb.app.model.aas.CustomConceptDescription;
import de.fraunhofer.iosb.app.model.aas.CustomSemanticId;
import de.fraunhofer.iosb.app.model.aas.CustomSubmodel;
import de.fraunhofer.iosb.app.model.aas.CustomSubmodelElement;
import de.fraunhofer.iosb.app.model.aas.CustomSubmodelElementCollection;
import de.fraunhofer.iosb.app.model.aas.Identifier;
import de.fraunhofer.iosb.app.util.AASUtil;
import de.fraunhofer.iosb.app.util.Encoder;
import de.fraunhofer.iosb.app.util.HttpRestClient;
Expand All @@ -29,14 +50,6 @@
import io.adminshell.aas.v3.model.impl.DefaultSubmodel;
import jakarta.ws.rs.core.Response;
import okhttp3.OkHttpClient;
import org.eclipse.edc.spi.EdcException;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;

import static java.lang.String.format;

/**
* Communicating with AAS service
Expand Down Expand Up @@ -123,11 +136,11 @@ public Response deleteModel(URL aasServiceUrl, String element) {
* @return AAS model enriched with each elements access URL as string in assetId
* field.
*/
public CustomAssetAdministrationShellEnvironment getAasEnvWithUrls(URL aasServiceUrl)
public CustomAssetAdministrationShellEnvironment getAasEnvWithUrls(URL aasServiceUrl, boolean onlySubmodels)
throws IOException, DeserializationException {
var aasServiceUrlString = aasServiceUrl.toString();

var model = readModel(aasServiceUrl);
var model = readModel(aasServiceUrl, onlySubmodels);
// Add urls to all shells
model.getAssetAdministrationShells().forEach(shell -> shell.setSourceUrl(
format("%s/shells/%s", aasServiceUrlString,
Expand Down Expand Up @@ -158,38 +171,53 @@ public CustomAssetAdministrationShellEnvironment getAasEnvWithUrls(URL aasServic
/**
* Returns the AAS model.
*/
private CustomAssetAdministrationShellEnvironment readModel(URL aasServiceUrl)
private CustomAssetAdministrationShellEnvironment readModel(URL aasServiceUrl, boolean onlySubmodels)
throws IOException, DeserializationException {
var aasEnv = new CustomAssetAdministrationShellEnvironment();

String shellResponse;
String conceptResponse;
String submodelResponse;
URL shellsUrl;
URL conceptDescriptionsUrl;
URL submodelUrl;
try {
shellResponse = Objects
.requireNonNull(httpRestClient.get(aasServiceUrl.toURI().resolve("/shells").toURL()).body())
.string();
submodelResponse = Objects
.requireNonNull(httpRestClient.get(aasServiceUrl.toURI().resolve("/submodels").toURL()).body())
.string();
conceptResponse = Objects.requireNonNull(httpRestClient.get(aasServiceUrl.toURI().resolve("/concept" +
"-descriptions").toURL()).body())
.string();
} catch (URISyntaxException e) {
throw new EdcException(e.getMessage());
submodelUrl = aasServiceUrl.toURI().resolve("/submodels").toURL();
shellsUrl = aasServiceUrl.toURI().resolve("/shells").toURL();
conceptDescriptionsUrl = aasServiceUrl.toURI().resolve("/concept-descriptions").toURL();
} catch (URISyntaxException resolveUriException) {
throw new EdcException(
format("Error while building URLs for reading from the AAS Service at %s", aasServiceUrl),
resolveUriException);
}

CustomAssetAdministrationShell[] shells = objectMapper.readValue(shellResponse,
CustomAssetAdministrationShell[].class);
CustomConceptDescription[] conceptDescriptions = objectMapper.readValue(conceptResponse,
CustomConceptDescription[].class);
aasEnv.setSubmodels(readSubmodels(submodelUrl, onlySubmodels));
if (!onlySubmodels) {
aasEnv.setAssetAdministrationShells(readShells(shellsUrl));
aasEnv.setConceptDescriptions(readConceptDescriptions(conceptDescriptionsUrl));
}
return aasEnv;
}

private List<CustomConceptDescription> readConceptDescriptions(URL conceptDescriptionsUrl) throws IOException {

var conceptResponse = Objects.requireNonNull(httpRestClient.get(conceptDescriptionsUrl).body()).string();

// Because of SMCs "value" field, submodels have to be parsed manually
return Arrays.asList(objectMapper.readValue(conceptResponse, CustomConceptDescription[].class));
}

private List<CustomAssetAdministrationShell> readShells(URL shellsUrl) throws IOException {
var shellHttpResponse = Objects.requireNonNull(httpRestClient.get(shellsUrl).body()).string();

return Arrays.asList(objectMapper.readValue(shellHttpResponse, CustomAssetAdministrationShell[].class));
}

// First, parse into full admin-shell.io submodels:
private List<CustomSubmodel> readSubmodels(URL submodelUrl, boolean onlySubmodels)
throws IOException, DeserializationException {
var submodelHttpResponse = Objects.requireNonNull(httpRestClient.get(submodelUrl).body()).string();

// First, parse into "full" admin-shell.io submodels:
JsonDeserializer jsonDeserializer = new JsonDeserializer();
List<DefaultSubmodel> submodels = jsonDeserializer.readReferables(submodelResponse, DefaultSubmodel.class);
List<DefaultSubmodel> submodels = jsonDeserializer.readReferables(submodelHttpResponse, DefaultSubmodel.class);

// Now, create custom submodels from the data of the full submodels
// Now, create customSubmodels from the "full" submodels
List<CustomSubmodel> customSubmodels = new ArrayList<>();
for (Submodel submodel : submodels) {
var customSubmodel = new CustomSubmodel();
Expand All @@ -202,19 +230,14 @@ private CustomAssetAdministrationShellEnvironment readModel(URL aasServiceUrl)
customSubmodel.setSemanticId(new CustomSemanticId(submodel.getSemanticId().getKeys()));
}

// Recursively add submodelElements
var customElements = AASUtil.getCustomSubmodelElementStructureFromSubmodel(submodel);
customSubmodel.setSubmodelElements((List<CustomSubmodelElement>) customElements);

if (!onlySubmodels) {
// Recursively add submodelElements
var customElements = AASUtil.getCustomSubmodelElementStructureFromSubmodel(submodel);
customSubmodel.setSubmodelElements((List<CustomSubmodelElement>) customElements);
}
customSubmodels.add(customSubmodel);
}
var aasEnv = new CustomAssetAdministrationShellEnvironment();

aasEnv.setAssetAdministrationShells(Arrays.asList(shells));
aasEnv.setSubmodels(customSubmodels);
aasEnv.setConceptDescriptions(Arrays.asList(conceptDescriptions));

return aasEnv;
return customSubmodels;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ public Response handleRequest(RequestType requestType, URL url, String... reques
* @throws DeserializationException AAS from service could not be deserialized
* @throws IOException Communication with AAS service failed
*/
public CustomAssetAdministrationShellEnvironment getAasModelWithUrls(URL aasServiceUrl)
public CustomAssetAdministrationShellEnvironment getAasModelWithUrls(URL aasServiceUrl, boolean onlySubmodels)
throws IOException, DeserializationException {
Objects.requireNonNull(aasServiceUrl);
return aasAgent.getAasEnvWithUrls(aasServiceUrl);
return aasAgent.getAasEnvWithUrls(aasServiceUrl, onlySubmodels);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@
package de.fraunhofer.iosb.app.model.aas;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;

import java.util.ArrayList;
import java.util.List;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonIgnoreProperties(ignoreUnknown = true)
public class CustomAssetAdministrationShellEnvironment {

protected List<CustomAssetAdministrationShell> assetAdministrationShells;
protected List<CustomAssetAdministrationShell> assetAdministrationShells = new ArrayList<>();

protected List<CustomSubmodel> submodels;
protected List<CustomSubmodel> submodels= new ArrayList<>();

protected List<CustomConceptDescription> conceptDescriptions;
protected List<CustomConceptDescription> conceptDescriptions= new ArrayList<>();

public List<CustomAssetAdministrationShell> getAssetAdministrationShells() {
return assetAdministrationShells;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;

import java.util.ArrayList;
import java.util.List;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonAutoDetect
public class CustomSubmodel extends AASElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public class Configuration {

@JsonProperty(SETTINGS_PREFIX + "syncPeriod")
private int syncPeriod = 5; // Seconds

@JsonProperty(SETTINGS_PREFIX + "onlySubmodels")
private boolean onlySubmodels = false;

@JsonProperty(SETTINGS_PREFIX + "exposeSelfDescription")
private boolean exposeSelfDescription = true;
Expand Down Expand Up @@ -80,6 +83,14 @@ public int getSyncPeriod() {
return syncPeriod;
}

public boolean isOnlySubmodels() {
return onlySubmodels;
}

public void setOnlySubmodels(boolean onlySubmodels) {
this.onlySubmodels = onlySubmodels;
}

public boolean isExposeSelfDescription() {
return exposeSelfDescription;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import de.fraunhofer.iosb.app.controller.AasController;
import de.fraunhofer.iosb.app.controller.ResourceController;
import de.fraunhofer.iosb.app.model.aas.*;
import de.fraunhofer.iosb.app.model.configuration.Configuration;
import de.fraunhofer.iosb.app.model.ids.SelfDescription;
import de.fraunhofer.iosb.app.model.ids.SelfDescriptionChangeListener;
import de.fraunhofer.iosb.app.model.ids.SelfDescriptionRepository;
Expand All @@ -43,12 +44,14 @@ public class Synchronizer implements SelfDescriptionChangeListener {
private final SelfDescriptionRepository selfDescriptionRepository;
private final AasController aasController;
private final ResourceController resourceController;
private final Configuration configuration;

public Synchronizer(SelfDescriptionRepository selfDescriptionRepository,
AasController aasController, ResourceController resourceController) {
this.selfDescriptionRepository = selfDescriptionRepository;
this.aasController = aasController;
this.resourceController = resourceController;
this.configuration = Configuration.getInstance();
}

/**
Expand All @@ -62,10 +65,12 @@ public void synchronize() {
}

private void synchronize(URL aasServiceUrl) {
var onlySubmodels = this.configuration.isOnlySubmodels();

var oldSelfDescription = selfDescriptionRepository.getSelfDescription(aasServiceUrl);
CustomAssetAdministrationShellEnvironment newEnvironment;
var newEnvironment = fetchCurrentAasModel(aasServiceUrl, onlySubmodels);

newEnvironment = fetchCurrentAasModel(aasServiceUrl);
// Only load submodels or shells, conceptDescriptions, submodelElements as well?

if (Objects.nonNull(oldSelfDescription)) {
var oldEnvironment = oldSelfDescription.getEnvironment();
Expand All @@ -77,9 +82,10 @@ private void synchronize(URL aasServiceUrl) {
// If the element exists in oldEnvironment, copy the old elements into
// newEnvironment, already having an idsContractId/idsAssetId
syncShell(newEnvironment, oldEnvironment);
syncSubmodel(newEnvironment, oldEnvironment);
syncConceptDescription(newEnvironment, oldEnvironment);

syncSubmodel(newEnvironment, oldEnvironment);

removeOldElements(newEnvironment, oldEnvironment);

// Finally, update the self description
Expand All @@ -88,11 +94,11 @@ private void synchronize(URL aasServiceUrl) {
selfDescriptionRepository.updateSelfDescription(aasServiceUrl, newEnvironment);
}

private CustomAssetAdministrationShellEnvironment fetchCurrentAasModel(URL aasServiceUrl) {
private CustomAssetAdministrationShellEnvironment fetchCurrentAasModel(URL aasServiceUrl, boolean onlySubmodels) {
CustomAssetAdministrationShellEnvironment newEnvironment;

try { // Fetch current AAS model from AAS service
newEnvironment = aasController.getAasModelWithUrls(aasServiceUrl);
newEnvironment = aasController.getAasModelWithUrls(aasServiceUrl, onlySubmodels);
} catch (IOException aasServiceUnreachableException) {
throw new EdcException(format("Could not reach AAS service (%s): %s", aasServiceUrl,
aasServiceUnreachableException.getMessage()));
Expand All @@ -105,7 +111,8 @@ private CustomAssetAdministrationShellEnvironment fetchCurrentAasModel(URL aasSe

private void addNewElements(CustomAssetAdministrationShellEnvironment newEnvironment) {
var envElements = AASUtil.getAllElements(newEnvironment);
addAssetsContracts(envElements.stream().filter(element -> Objects.isNull(element.getIdsAssetId()) || Objects.isNull(element.getIdsContractId()))
addAssetsContracts(envElements.stream().filter(
element -> Objects.isNull(element.getIdsAssetId()) || Objects.isNull(element.getIdsContractId()))
.toList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,27 @@ public void testGetAasEnvWithUrls() throws IOException, DeserializationException
var shouldBeResult = FileManager.loadResource("selfDescriptionWithAccessURLS.json");

var result = new ObjectMapper().writeValueAsString(
aasAgent.getAasEnvWithUrls(new URL(HTTP_LOCALHOST_8080)));
aasAgent.getAasEnvWithUrls(new URL(HTTP_LOCALHOST_8080), false));
result = result.replace("\n", "").replace(" ", "");

assertEquals(shouldBeResult, result);
}

@Test
public void testGetAasEnvWithUrlsOnlySubmodels() throws IOException, DeserializationException {
var shells = FileManager.loadResource("shells.json");
var submodels = FileManager.loadResource("submodels.json");
var conceptDescriptions = FileManager.loadResource("conceptDescriptions.json");

mockServer.when(request().withMethod("GET").withPath("/shells")).respond(response().withBody(shells));
mockServer.when(request().withMethod("GET").withPath("/submodels")).respond(response().withBody(submodels));
mockServer.when(request().withMethod("GET").withPath("/concept-descriptions"))
.respond(response().withBody(conceptDescriptions));

var shouldBeResult = FileManager.loadResource("selfDescriptionWithAccessURLsSubmodelsOnly.json");

var result = new ObjectMapper().writeValueAsString(
aasAgent.getAasEnvWithUrls(new URL(HTTP_LOCALHOST_8080), true));
result = result.replace("\n", "").replace(" ", "");

assertEquals(shouldBeResult, result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class SelfDescriptionTest {
@Test
public void emptyEnvironmentTest() {
SelfDescription selfDescription = new SelfDescription(new CustomAssetAdministrationShellEnvironment());
assertEquals("{\"assetAdministrationShells\":null,\"submodels\":null,\"conceptDescriptions\":null}",
assertEquals("{}",
selfDescription.toString());
}
}
Loading

0 comments on commit 7642297

Please sign in to comment.