diff --git a/basyx.submodelservice/basyx.submodelservice.component/Dockerfile b/basyx.submodelservice/basyx.submodelservice.component/Dockerfile new file mode 100644 index 000000000..d86fda09c --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/Dockerfile @@ -0,0 +1,14 @@ +FROM amazoncorretto:17 +USER nobody +WORKDIR /application +ARG JAR_FILE=target/*-exec.jar +COPY ${JAR_FILE} basyxExecutable.jar +ARG AAS4J_JAR_FILE=target/libs/aas4j-model-1.0.2.jar +COPY ${AAS4J_JAR_FILE} libs/aas4j-model-1.0.2.jar +ARG PORT=8081 +ENV SERVER_PORT=${PORT} +ARG CONTEXT_PATH=/ +ENV SERVER_SERVLET_CONTEXT_PATH=${CONTEXT_PATH} +EXPOSE ${SERVER_PORT} +HEALTHCHECK --interval=10s --timeout=3s --retries=10 --start-period=5s CMD curl --fail http://localhost:${SERVER_PORT}${SERVER_SERVLET_CONTEXT_PATH%/}/actuator/health || exit 1 +ENTRYPOINT ["java", "-jar", "basyxExecutable.jar"] diff --git a/basyx.submodelservice/basyx.submodelservice.component/Readme.md b/basyx.submodelservice/basyx.submodelservice.component/Readme.md new file mode 100644 index 000000000..39101706b --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/Readme.md @@ -0,0 +1,91 @@ +# Eclipse BaSyx - Standalone Submodel Service Component + +This project provides a generic Submodel Service component. With this component, you can deploy your own Submodel Services without the need to build a Spring Boot application or create and deploy your own Dockerfiles. + +*Invoke* calls to the Submodel element *Operation* are delegated in the generic component to Java classes that can either be precompiled or provided as source code. + +These methods can be provided as simple Java classes and do not require additional dependencies for the project. At runtime, the classes from the *aas4j-model* module are already available. Additional libraries can be included via an environment variable. + +## Configuration + +The [example folder](./example/) contains sample settings. For assigning `idShortPaths` to executable Java classes, configuration should be done using Properties in the form of a Properties file or YAML. Environment variables are not processed correctly by Spring when mapping. + +When running the container, the execution folder is /application. + +**Performance Note**: The startup time of the service can be significantly reduced if the sources are already compiled and placed as JAR files or class files in the /application directory. Pre-compiling your code before building the Docker image can improve performance and reduce initialization time. + +### System Properties + +Below are the individual properties used when starting the application. Except for `basyx.submodel.file`, all properties are optional. + +| Property | Example | Explanation | +|-------------------------------------------------------|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------| +| `basyx.submodel.file` | `submodel.json` | Path to the file describing the Submodel. | +| `basyx.operation.java.sourcesPath` | `sources` | Source directory where the Java classes to be compiled are located. These classes can be specified to be loaded at runtime to perform operations. | +| `basyx.operation.java.classesPath` | `classes` | Target directory for compilation. Used in the classpath when loading classes. | +| `basyx.operation.java.additionalClasspath` | `jars/HelloWorld.jar,jars/` | Comma-separated list of additional libraries used during source compilation and loading of executable classes. | +| `basyx.operation.invokation.mappings[SquareOperation]` | `org.example.SquareOp` | Example of a mapping assignment. The `idShortPath` of an operation is assigned to the class `org.example.SquareOp`. | +| `basyx.operation.invokation.mappings[BasicOperations.AddOperation]` | `org.basic.AddOperation` | Another example of mapping an operation to a Java class. | +| `basyx.operation.invokation.defaultMapping` | `org.example.MyOperation` | Specifies the operation to be called if no mapping is found. | + +The example project contains an [application.yml](./example/application.yml) that demonstrates how configuration can be specified clearly using YAML. + +### Structure of Java Classes + +The Java classes that execute operations do not require dependencies and do not need to extend interfaces or implement classes. It is important that they include a method with the following signature: + +```java +public OperationVariable[] invoke(String path, Operation op, OperationVariable[] in) +``` + +Alternatively, the following method can be used if not all arguments are needed: + +```java +public OperationVariable[] invoke(OperationVariable[] in) +``` + +Here is a simple example: + +```java +package org.basic; + +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.Operation; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; + +public class AddOperation { + + public OperationVariable[] invoke(String path, Operation op, OperationVariable[] in) { + Property first = (Property) in[0].getValue(); + Property second = (Property) in[1].getValue(); + int iFirst = Integer.parseInt(first.getValue()); + int iSecond = Integer.parseInt(second.getValue()); + int result = iFirst + iSecond; + Property prop = new DefaultProperty.Builder() + .value(String.valueOf(result)) + .valueType(DataTypeDefXsd.INT) + .build(); + return new OperationVariable[] { new DefaultOperationVariable.Builder().value(prop).build() }; + } +} +``` + +The execution is stateless. A new instance is created each time. + +### Creating a Custom Image + +This setup allows for quick deployment of Submodel Services. However, if you want to avoid configuration and Docker volume binding, you can also create your own images quickly: + +```dockerfile +FROM eclipsebasyx/submodel-service:0.2.0-SNAPSHOT +COPY sources/ /application/sources +COPY jars/ /application/jars +COPY submodel.json /application/submodel.json +COPY application.yml /application/config/application.yml +``` + +The operations can also be precompiled and placed as JARs in the `/application/jars` folder, which is referenced in the `application.yml`. Alternatively, you can place Java classes of the correct syntax in the source folder and reference them in the properties via a mapping. + diff --git a/basyx.submodelservice/basyx.submodelservice.component/example/Dockerfile.standalone-example b/basyx.submodelservice/basyx.submodelservice.component/example/Dockerfile.standalone-example new file mode 100644 index 000000000..42e6ceb69 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/example/Dockerfile.standalone-example @@ -0,0 +1,5 @@ +FROM eclipsebasyx/submodel-service:0.2.0-SNAPSHOT +COPY sources/ /application/sources +COPY jars/ /application/jars +COPY submodel.json /application/submodel.json +COPY application.yml /application/config/application.yml \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/example/Readme.md b/basyx.submodelservice/basyx.submodelservice.component/example/Readme.md new file mode 100644 index 000000000..e6a80b23f --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/example/Readme.md @@ -0,0 +1,19 @@ +## Submodel Service Component Example + +This example describes a potential setup for the Submodel Service using Docker Compose. + +## Configuration + +The [docker-compose.yml](docker-compose.yml) file provides a basic setup for starting the service. The Docker image used is initially built based on the [Dockerfile located in the parent directory](../Dockerfile). + +Volumes are used to provide the Submodel and the executable source code to the container, which are referenced in the environment section. The mapping of `idShortPath` to Java classes is also referenced there and loaded via [application-mappings.yml](application-mappings.yml). Alternatively, you can simplify the setup by configuring everything directly in [application.yml](application.yml). + +**Performance Note:** For faster startup times, it's recommended to pre-compile your code and provide it as JAR files or class files. The [aas4j](https://github.com/eclipse-aas4j/aas4j) model classes are available at runtime and do not need to be added to the classpath. + +### Test Script + +Please review the [start-container.sh](start-container.sh) shell script and execute it. The script first builds the executable JAR using Maven, if necessary, which is then copied into the Docker image. After that, the Docker Compose stack is started, and test cases are executed. + +## Standalone Image + +The [Dockerfile.standalone-example](Dockerfile.standalone-example) provides an example of how a Dockerfile for a standalone service might look. When using standalone images, mounting additional volumes or setting environment variables is not strictly necessary. For standalone images, it's recommended to provide precompiled classes or a JAR file to expedite the service startup. The `aas4j` model classes are available at runtime and do not need to be added to the classpath. diff --git a/basyx.submodelservice/basyx.submodelservice.component/example/application-mappings.yml b/basyx.submodelservice/basyx.submodelservice.component/example/application-mappings.yml new file mode 100644 index 000000000..46e18b862 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/example/application-mappings.yml @@ -0,0 +1,7 @@ +basyx: + operation: + invokation: + mappings: + SquareOperation: SquareOperation + BasicOperations.AddOperation: basic.ops.AddOperation + BasicOperations.HelloOperation: HelloOperation \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/example/application.yml b/basyx.submodelservice/basyx.submodelservice.component/example/application.yml new file mode 100644 index 000000000..35c15678f --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/example/application.yml @@ -0,0 +1,14 @@ +basyx: + submodel: + file: submodel.json + operation: + java: + sourcesPath: sources + classesPath: classes + additionalClasspath: + - jars/HelloWorld.jar + invokation: + mappings: + SquareOperation: SquareOperation + "BasicOperations.AddOperation": basic.ops.AddOperation + BasicOperations.HelloOperation: HelloOperation \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/example/docker-compose.yml b/basyx.submodelservice/basyx.submodelservice.component/example/docker-compose.yml new file mode 100644 index 000000000..6711a894c --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/example/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3.9" +services: + submodel-service: + image: eclipsebasyx/submodel-service:test + build: + context: .. + container_name: submodel-service + environment: + BASYX_SUBMODEL_FILE: submodel.json + BASYX_OPERATION_JAVA_SOURCESPATH: sources + BASYX_OPERATION_JAVA_CLASSESPATH: classes + BASYX_OPERATION_JAVA_ADDITIONALCLASSPATH: jars/HelloWorld.jar + # we should use properties or yml files for operation mappings + # Environment variables could not be parsed properly + # dots of the idShortPath are not handled properly by spring and also the lower-case conversion of spring is problematic + # so either we use config file for mappings or define everything in a .properties or .yml file + SPRING_PROFILES_ACTIVE: mappings + ports: + - 8111:8081 + volumes: + - ./submodel.json:/application/submodel.json:ro + - ./sources/:/application/sources/:ro + - ./jars/:/application/jars/:ro + - ./application-mappings.yml:/application/config/application-mappings.yml/:ro diff --git a/basyx.submodelservice/basyx.submodelservice.component/example/jars/HelloWorld.jar b/basyx.submodelservice/basyx.submodelservice.component/example/jars/HelloWorld.jar new file mode 100644 index 000000000..b750c13f9 Binary files /dev/null and b/basyx.submodelservice/basyx.submodelservice.component/example/jars/HelloWorld.jar differ diff --git a/basyx.submodelservice/basyx.submodelservice.component/example/sources/HelloOperation.java b/basyx.submodelservice/basyx.submodelservice.component/example/sources/HelloOperation.java new file mode 100644 index 000000000..af1f0562d --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/example/sources/HelloOperation.java @@ -0,0 +1,15 @@ +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; + +public class HelloOperation { + + public OperationVariable[] invoke(OperationVariable[] in) { + String value = new HelloWorld().sayHello(); + Property prop = new DefaultProperty.Builder().value(value).valueType(DataTypeDefXsd.STRING).build(); + return new OperationVariable[] {new DefaultOperationVariable.Builder().value(prop).build()}; + } + +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/example/sources/SquareOperation.java b/basyx.submodelservice/basyx.submodelservice.component/example/sources/SquareOperation.java new file mode 100644 index 000000000..f80825115 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/example/sources/SquareOperation.java @@ -0,0 +1,17 @@ +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; + +public class SquareOperation { + + public OperationVariable[] invoke(OperationVariable[] vars) { + Property prop = (Property) vars[0].getValue(); + int value = Integer.parseInt(prop.getValue()); + int result = value * value; + Property toReturn = new DefaultProperty.Builder().value(String.valueOf(result)).valueType(DataTypeDefXsd.INT).build(); + return new OperationVariable[] {new DefaultOperationVariable.Builder().value(toReturn).build()}; + } + +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/example/sources/basic/ops/AddOperation.java b/basyx.submodelservice/basyx.submodelservice.component/example/sources/basic/ops/AddOperation.java new file mode 100644 index 000000000..ee6f1bfee --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/example/sources/basic/ops/AddOperation.java @@ -0,0 +1,24 @@ +package basic.ops; + +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.Operation; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; + +public class AddOperation { + + public OperationVariable[] invoke(String path, Operation op, OperationVariable[] in) { + Property first = (Property) in[0].getValue(); + Property second = (Property) in[1].getValue(); + int iFirst = Integer.parseInt(first.getValue()); + int iSecond = Integer.parseInt(second.getValue()); + int result = iFirst + iSecond; + Property prop = new DefaultProperty.Builder().value(String.valueOf(result)).valueType(DataTypeDefXsd.INT).build(); + return new OperationVariable[] {new DefaultOperationVariable.Builder().value(prop).build()}; + } + +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/example/start-container.sh b/basyx.submodelservice/basyx.submodelservice.component/example/start-container.sh new file mode 100644 index 000000000..db54beb63 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/example/start-container.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +project_was_built() { + if find ../target -type f -name "basyx.submodelservice.component*-exec.jar" | grep -q "^"; then + return 0 + else + return 1 + fi +} + +build_maven() { + echo building maven artifacts ... + mvn -f ../../../pom.xml clean install -DskipTests +} + +run_square() { + echo "Please enter an integer value:" + read -r int_value + response=$(curl http://localhost:8111/submodel/submodel-elements/SquareOperation/invoke \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d "{ \"inputArguments\" : [{ \"value\" : { \"modelType\" : \"Property\", \"value\" : \"${int_value}\" }}]}" \ + -s | jq '.outputArguments[0].value.value') + echo result: $response +} + + +run_add() { + echo "Please enter the first integer value:" + read -r int_value1 + echo "Please enter the second integer value:" + read -r int_value2 + response=$(curl http://localhost:8111/submodel/submodel-elements/BasicOperations.AddOperation/invoke \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d "{ \"inputArguments\" : [{ \"value\" : { \"modelType\" : \"Property\", \"value\" : \"${int_value1}\" }},{ \"value\" : { \"modelType\" : \"Property\", \"value\" : \"${int_value2}\" }}]}" \ + -s | jq '.outputArguments[0].value.value') + echo result: $response +} + +run_hello() { + response=$(curl http://localhost:8111/submodel/submodel-elements/BasicOperations.HelloOperation/invoke \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d "{ }" \ + -s | jq '.outputArguments[0].value.value') + echo result: $response +} + +run_tests() { + echo We are now running tests with curl + + echo The following operations can be invoked: + echo "'s': square operation" + echo "'h': hello world operation" + echo "'a': add operation" + echo "'e': exit" + while true; do + # Nutzer nach Eingabe fragen + echo "Please enter 's', 'h', 'a' or 'e' to exit:" + read -r user_input + + # Prüfen, welche Eingabe gemacht wurde + if [[ "$user_input" == "s" ]]; then + run_square + elif [[ "$user_input" == "h" ]]; then + run_hello + elif [[ "$user_input" == "a" ]]; then + run_add + elif [[ "$user_input" == "e" ]]; then + echo Exit... + break + else + echo "Input not valid. Please enter 's', 'h', 'a' or 'e'." + fi + done +} + +if ! project_was_built; then + read -p "The maven artifact does not exist. Do you want to build all maven artifacts now? [Y/n]: " build_maven + build_maven=${build_maven:-Y} + if [[ "$build_maven" =~ ^[Yy]$ ]]; then + build_maven; + else + echo abort + return; + fi +fi + +docker-compose up --build --force-recreate --detach --wait +echo service started: http://localhost:8111/submodel + +run_tests; + +echo "Shutting down containers" +docker-compose down diff --git a/basyx.submodelservice/basyx.submodelservice.component/example/submodel.json b/basyx.submodelservice/basyx.submodelservice.component/example/submodel.json new file mode 100644 index 000000000..860e239df --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/example/submodel.json @@ -0,0 +1,124 @@ +{ + "modelType": "Submodel", + "id": "Example", + "idShort": "example", + "kind": "Instance", + "semanticId": { + "keys": [ + { + "value": "123" + } + ] + }, + "category": "TestCategory", + "description": [ + { + "language": "de-DE", + "text": "Test" + } + ], + "displayName": [ + { + "language": "de-DE", + "text": "Test" + } + ], + "submodelElements": [ + { + "modelType": "Property", + "value": "123", + "idShort": "test" + }, + { + "modelType": "Operation", + "inputVariables": [ + { + "value": { + "modelType": "Property", + "valueType": "xs:int", + "idShort": "input" + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "valueType": "xs:int", + "idShort": "result" + } + } + ], + "idShort": "SquareOperation" + }, + { + "modelType": "SubmodelElementCollection", + "idShort" : "BasicOperations", + "value": [ + { + "modelType": "Operation", + "inputVariables": [ + { + "value": { + "modelType": "Property", + "valueType": "xs:int" + } + }, + { + "value": { + "modelType": "Property", + "valueType": "xs:int" + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "valueType": "xs:int", + "idShort": "result" + } + } + ], + "idShort": "AddOperation" + }, + { + "modelType": "Operation", + "inputVariables": [ + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "valueType": "xs:string", + "idShort": "result" + } + } + ], + "idShort": "HelloOperation" + }, + { + "modelType": "Operation", + "inputVariables": [ + { + "value": { + "modelType": "Property", + "valueType": "xs:int" + } + } + ], + "outputVariables": [ + { + "value": { + "modelType": "Property", + "valueType": "xs:int", + "idShort": "result" + } + } + ], + "idShort": "UnassignedOperation" + } + ] + } + ] +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/pom.xml b/basyx.submodelservice/basyx.submodelservice.component/pom.xml new file mode 100644 index 000000000..9b38824e5 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/pom.xml @@ -0,0 +1,166 @@ + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelservice + ${revision} + + basyx.submodelservice.component + BaSyx submodelservice.component + BaSyx submodelservice.component + + + submodel-service + + http://localhost:${docker.host.port}/submodel + + + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-backend-inmemory + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-http + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + download-artifact + + copy + + validate + + + + org.eclipse.digitaltwin.aas4j + aas4j-model + 1.0.2 + jar + + ${project.build.directory}/libs + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + exec + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + libs/aas4j-model-1.0.2.jar + + + + + + + + + + + + docker + + + docker.namespace + + + + + + io.fabric8 + docker-maven-plugin + + + + + + + ${docker.host.port}:${docker.container.port} + + + + ${docker.container.waitForEndpoint} + + + + + + + + + build-docker + + + push-docker + + + docker-compose-up + pre-integration-test + + start + + + + docker-compose-down + post-integration-test + + stop + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/AbstractSubmodelServiceDecorator.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/AbstractSubmodelServiceDecorator.java new file mode 100644 index 000000000..cfc0c06e9 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/AbstractSubmodelServiceDecorator.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component; + +import java.io.File; +import java.io.InputStream; +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; +import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +public class AbstractSubmodelServiceDecorator implements SubmodelService { + + private final SubmodelService decorated; + + public AbstractSubmodelServiceDecorator(SubmodelService decorated) { + this.decorated = decorated; + } + + @Override + public Submodel getSubmodel() { + return decorated.getSubmodel(); + } + + @Override + public CursorResult> getSubmodelElements(PaginationInfo pInfo) { + return decorated.getSubmodelElements(pInfo); + } + + @Override + public SubmodelElement getSubmodelElement(String idShortPath) throws ElementDoesNotExistException { + return decorated.getSubmodelElement(idShortPath); + } + + @Override + public SubmodelElementValue getSubmodelElementValue(String idShortPath) throws ElementDoesNotExistException { + return decorated.getSubmodelElementValue(idShortPath); + } + + @Override + public void setSubmodelElementValue(String idShortPath, SubmodelElementValue value) + throws ElementDoesNotExistException { + decorated.setSubmodelElementValue(idShortPath, value); + } + + @Override + public void createSubmodelElement(SubmodelElement submodelElement) { + decorated.createSubmodelElement(submodelElement); + } + + @Override + public void createSubmodelElement(String idShortPath, SubmodelElement submodelElement) + throws ElementDoesNotExistException { + decorated.createSubmodelElement(idShortPath, submodelElement); + } + + @Override + public void updateSubmodelElement(String idShortPath, SubmodelElement submodelElement) + throws ElementDoesNotExistException { + decorated.updateSubmodelElement(idShortPath, submodelElement); + } + + @Override + public void deleteSubmodelElement(String idShortPath) throws ElementDoesNotExistException { + decorated.deleteSubmodelElement(idShortPath); + } + + @Override + public void patchSubmodelElements(List submodelElementList) { + decorated.patchSubmodelElements(submodelElementList); + } + + @Override + public OperationVariable[] invokeOperation(String idShortPath, OperationVariable[] input) + throws ElementDoesNotExistException { + return decorated.invokeOperation(idShortPath, input); + } + + @Override + public File getFileByPath(String idShortPath) + throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + return decorated.getFileByPath(idShortPath); + } + + @Override + public void setFileValue(String idShortPath, String fileName, InputStream inputStream) + throws ElementDoesNotExistException, ElementNotAFileException { + decorated.setFileValue(idShortPath, fileName, inputStream); + } + + @Override + public void deleteFileValue(String idShortPath) + throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + decorated.deleteFileValue(idShortPath); + } + + @Override + public InputStream getFileByFilePath(String filePath) { + return decorated.getFileByFilePath(filePath); + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericOperationSubmodelService.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericOperationSubmodelService.java new file mode 100644 index 000000000..dd2a1a8d8 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericOperationSubmodelService.java @@ -0,0 +1,32 @@ +package org.eclipse.digitaltwin.basyx.submodelservice.component; + +import java.util.function.Function; + +import org.eclipse.digitaltwin.aas4j.v3.model.Operation; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; +import org.springframework.http.HttpStatusCode; +import org.springframework.web.server.ResponseStatusException; + +public class GenericOperationSubmodelService extends AbstractSubmodelServiceDecorator { + + private final Function invokableProvider; + + public GenericOperationSubmodelService(SubmodelService service, Function invokableProvider) { + super(service); + this.invokableProvider = invokableProvider; + } + + @Override + public OperationVariable[] invokeOperation(String idShortPath, OperationVariable[] input) + throws ElementDoesNotExistException { + SubmodelElement elem = getSubmodelElement(idShortPath); + if (elem == null || !(elem instanceof Operation)) { + throw new ResponseStatusException(HttpStatusCode.valueOf(404)); + } + OperationInvokation invokation = invokableProvider.apply(idShortPath); + return invokation.invoke(idShortPath, (Operation)elem, input); + } +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericSubmodelComponent.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericSubmodelComponent.java new file mode 100644 index 000000000..521cede17 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericSubmodelComponent.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx") +public class GenericSubmodelComponent { + + public static void main(String[] args) { + SpringApplication.run(GenericSubmodelComponent.class, args); + } + +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericSubmodelConfiguration.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericSubmodelConfiguration.java new file mode 100644 index 000000000..8793515a0 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericSubmodelConfiguration.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component; + +import java.util.function.Function; + +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; +import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + + +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +@Configuration +public class GenericSubmodelConfiguration { + + @Bean + public SubmodelService getSubmodelService(Submodel submodel, SubmodelServiceFactory factory) { + return factory.create(submodel); + } + + @Bean + public Submodel getSubmodel(GenericSubmodelFactory factory) { + return factory.create(); + } + + @Bean + @Primary + public SubmodelServiceFactory getSubmodelServiceFactory(SubmodelServiceFactory smFactory, InvokableFactory iFactory) { + Function invokation = iFactory.createInvokableProvider(); + return new GenericOperationSubmodelServiceFactory(smFactory, invokation); + } + + private static class GenericOperationSubmodelServiceFactory implements SubmodelServiceFactory { + + private final SubmodelServiceFactory decorated; + private final Function invokableProvider; + + public GenericOperationSubmodelServiceFactory(SubmodelServiceFactory factory, Function invokableProvider) { + this.decorated = factory; + this.invokableProvider = invokableProvider; + } + + @Override + public SubmodelService create(Submodel submodel) { + return new GenericOperationSubmodelService(decorated.create(submodel), invokableProvider); + } + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericSubmodelFactory.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericSubmodelFactory.java new file mode 100644 index 000000000..a9d306cbf --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/GenericSubmodelFactory.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.model.Operation; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; +import org.eclipse.digitaltwin.basyx.InvokableOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * @author gerhard sonnenberg (DFKI GmbH) + */ +@Component +public class GenericSubmodelFactory { + + private final String filePath; + + @Autowired + public GenericSubmodelFactory(@Value("${basyx.submodel.file}") String filePath) { + this.filePath = filePath; + if (StringUtils.isEmpty(filePath)) { + throw new IllegalStateException("Application property 'basyx.submodel.file' not set."); + } + } + + public Submodel create() { + return loadSubmodel(); + } + + public Submodel loadSubmodel() { + File submodelFile = new File(filePath); + try ( FileInputStream fIn = new FileInputStream(submodelFile); BufferedInputStream bIn = new BufferedInputStream(fIn)) { + JsonDeserializer deserializer = new JsonDeserializer(); + deserializer.useImplementation(Operation.class, InvokableOperation.class); + return deserializer.read(bIn, DefaultSubmodel.class); + } catch (IOException | DeserializationException e) { + throw new IllegalStateException("Could not load submodel: " + submodelFile.getAbsolutePath(), e); + } + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/InvokableFactory.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/InvokableFactory.java new file mode 100644 index 000000000..3db00ed38 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/InvokableFactory.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component; + +import java.util.function.Function; + +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +@FunctionalInterface +public interface InvokableFactory { + + Function createInvokableProvider(); +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/OperationInvokation.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/OperationInvokation.java new file mode 100644 index 000000000..d3de5b243 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/OperationInvokation.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component; + +import org.eclipse.digitaltwin.aas4j.v3.model.Operation; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +public interface OperationInvokation { + + OperationVariable[] invoke(String path, Operation op, OperationVariable[] variables); +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/OperationMappingDefinition.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/OperationMappingDefinition.java new file mode 100644 index 000000000..400453424 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/OperationMappingDefinition.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component; + +import java.util.Collections; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +@Configuration +@ConfigurationProperties(prefix = "basyx.operation.invokation") +public class OperationMappingDefinition { + + private String defaultMapping; + + private Map mappings = Collections.emptyMap(); + + public void setDefaultMapping(String defaultMapping) { + this.defaultMapping = defaultMapping; + } + + public String getDefaultMapping() { + return defaultMapping; + } + + public void setMappings(Map mappings) { + this.mappings = mappings; + } + + public Map getMappings() { + return mappings; + } +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/DefaultInvokableOperation.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/DefaultInvokableOperation.java new file mode 100644 index 000000000..db38abb7b --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/DefaultInvokableOperation.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component.java; + +import org.eclipse.digitaltwin.aas4j.v3.model.Operation; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.springframework.http.HttpStatusCode; +import org.springframework.web.server.ResponseStatusException; + +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +public class DefaultInvokableOperation { + + public OperationVariable[] invoke(String path, Operation op, OperationVariable[] in) { + throw new ResponseStatusException(HttpStatusCode.valueOf(404), "No operation handling assigned for path '" + path + "'."); + } +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/DynamicJavaClassLoader.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/DynamicJavaClassLoader.java new file mode 100644 index 000000000..de6f4a47b --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/DynamicJavaClassLoader.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component.java; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ + +public class DynamicJavaClassLoader { + + private static final Logger LOGGER = LoggerFactory.getLogger(DynamicJavaClassLoader.class); + + public void compileClasses(Path sourcesFolder, Path classFolder, List additionalClassPath) + throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException, SecurityException, IOException { + + assertSourcePathExists(sourcesFolder); + assertClassesPathExists(classFolder); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); + fileManager.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, List.of(classFolder)); + + List paths = new LinkedList(additionalClassPath); + fileManager.getLocationAsPaths(StandardLocation.CLASS_PATH).forEach(paths::add); + LOGGER.info("Compiling classes with class path: " + paths); + + + fileManager.setLocationFromPaths(StandardLocation.CLASS_PATH, paths); + + List javaFiles = getJavaFiles(sourcesFolder); + Iterable compilationUnits = fileManager.getJavaFileObjectsFromPaths(javaFiles); + JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, compilationUnits); + boolean success = task.call(); + if (!success) { + throw new IllegalStateException("Failed to compile java operation classes."); + } + LOGGER.info("Classes compiled successfully."); + } + + private List getJavaFiles(Path sourcesFolder) throws IOException { + return Files.walk(sourcesFolder, Integer.MAX_VALUE).filter(this::isJavaFile).collect(Collectors.toList()); + } + + private boolean isJavaFile(Path path) { + return path.toString().endsWith(".java"); + } + + private void assertSourcePathExists(Path srcPath) { + if (!Files.exists(srcPath)) { + throw new IllegalStateException("Source folder '" + srcPath.toAbsolutePath() + "' does not exists."); + } + } + + private void assertClassesPathExists(Path clsPath) { + if (!Files.exists(clsPath)) { + try { + Files.createDirectories(clsPath); + } catch (IOException e) { + throw new IllegalStateException( + "Failed to create output directory '" + clsPath.toAbsolutePath() + "'."); + } + } + } + + public URLClassLoader loadClasses(Path classFolder, List additionalClassPath) throws IOException { + List urls = new ArrayList<>(); + if (classFolder != null) { + urls.add(safeToUrl(classFolder.toUri())); + } + if (additionalClassPath != null) { + for (Path eachAddtionalClassPath : additionalClassPath) { + urls.add(safeToUrl(eachAddtionalClassPath.toUri())); + } + } + return URLClassLoader.newInstance(urls.toArray(new URL[0]), DynamicJavaClassLoader.class.getClassLoader()); + } + + public URL safeToUrl(URI uri) { + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new IllegalStateException("Failed to convert uri to url", e); + } + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/JavaInvokableDefinition.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/JavaInvokableDefinition.java new file mode 100644 index 000000000..d2d46f79a --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/JavaInvokableDefinition.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component.java; + +import java.nio.file.Path; +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +@Configuration +@ConfigurationProperties(prefix = "basyx.operation.java") +public class JavaInvokableDefinition { + + private Path sourcesPath; + private Path classesPath; + private List additionalClasspath = List.of(); + + public Path getSourcesPath() { + return sourcesPath; + } + + public void setSourcesPath(Path sourcesPath) { + this.sourcesPath = sourcesPath; + } + + public Path getClassesPath() { + return classesPath; + } + + public void setClassesPath(Path classesPath) { + this.classesPath = classesPath; + } + + public List getAdditionalClasspath() { + return additionalClasspath; + } + + public void setAdditionalClasspath(List additionalClasspath) { + this.additionalClasspath = additionalClasspath; + } + +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/JavaInvokableFactory.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/JavaInvokableFactory.java new file mode 100644 index 000000000..9d172777c --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/JavaInvokableFactory.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component.java; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.eclipse.digitaltwin.basyx.submodelservice.component.InvokableFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.component.OperationInvokation; +import org.eclipse.digitaltwin.basyx.submodelservice.component.OperationMappingDefinition; +import org.springframework.stereotype.Component; + +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +@Component +public class JavaInvokableFactory implements InvokableFactory { + + private final ReflectionBasedOperationHandlerSupport handler; + + public JavaInvokableFactory(JavaInvokableDefinition definition, OperationMappingDefinition conf) + throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { + DynamicJavaClassLoader loader = new DynamicJavaClassLoader(); + Path sources = definition.getSourcesPath(); + Path classes = definition.getClassesPath(); + List additionalClasspath = definition.getAdditionalClasspath(); + if (sources != null) { + loader.compileClasses(sources, classes, additionalClasspath); + } + URLClassLoader urlClassLoader = loader.loadClasses(classes, additionalClasspath); + handler = new ReflectionBasedOperationHandlerSupport(urlClassLoader, conf.getMappings(), + conf.getDefaultMapping()); + } + + @Override + public Function createInvokableProvider() { + return this::createInvokable; + } + + public OperationInvokation createInvokable(String sPath) { + Map invokationMap = handler.getAssignedInvokations(); + return invokationMap.getOrDefault(sPath, handler.getDefaultMethodInvokation()); + } +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/ReflectionBasedOperationHandlerSupport.java b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/ReflectionBasedOperationHandlerSupport.java new file mode 100644 index 000000000..0e39a33e6 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/component/java/ReflectionBasedOperationHandlerSupport.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component.java; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URLClassLoader; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import org.eclipse.digitaltwin.aas4j.v3.model.Operation; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.basyx.submodelservice.component.OperationInvokation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +public class ReflectionBasedOperationHandlerSupport { + + private final URLClassLoader loader; + private final Map assignedInvokations; + private final OperationInvokation defaultInvokation; + + private static final Logger LOGGER = LoggerFactory.getLogger(DynamicJavaClassLoader.class); + + public ReflectionBasedOperationHandlerSupport(URLClassLoader loader, Map mappings, String defaultClass) { + this.loader = loader; + this.assignedInvokations = mappings.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e->getMethodInvoker(e.getValue()))); + this.defaultInvokation = getMethodInvoker(defaultClass); + mappings.forEach((k,v)->LOGGER.info("Operation class '{}' assigned for idShortPath '{}'.", v, k)); + } + + public OperationInvokation getDefaultMethodInvokation() { + return defaultInvokation; + } + + public Map getAssignedInvokations() { + return assignedInvokations; + } + + private OperationInvokation getMethodInvoker(String clsName) { + try { + Class cls = loader.loadClass(clsName); + Constructor constructor = cls.getConstructor(); + constructor.setAccessible(true); + try { + Method mtd = cls.getDeclaredMethod("invoke", String.class, Operation.class, + OperationVariable.class.arrayType()); + mtd.setAccessible(true); + return new MultiArgMethodInvoker(constructor, mtd); + } catch (NoSuchMethodException e) { + Method mtd = cls.getDeclaredMethod("invoke", OperationVariable.class.arrayType()); + mtd.setAccessible(true); + return new SingleArgMethodInvoker(constructor, mtd); + } + } catch (SecurityException | NoSuchMethodException | ClassNotFoundException e) { + throw new IllegalStateException("'invoke' Method not available.", e); + } + } + + public static class SingleArgMethodInvoker implements OperationInvokation { + + protected final Constructor constructor; + protected final Method mtd; + + public SingleArgMethodInvoker(Constructor constructor, Method mtd) { + this.constructor = constructor; + this.mtd = mtd; + } + + @Override + public OperationVariable[] invoke(String path, Operation op, OperationVariable[] variables) { + try { + Object obj = constructor.newInstance(); + return (OperationVariable[]) mtd.invoke(obj, (Object) variables); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + throw new IllegalStateException("Failed to invoke operation", e); + } + } + } + + public static class MultiArgMethodInvoker implements OperationInvokation { + + protected final Constructor constructor; + protected final Method mtd; + + public MultiArgMethodInvoker(Constructor constructor, Method mtd) { + this.constructor = constructor; + this.mtd = mtd; + } + + @Override + public OperationVariable[] invoke(String path, Operation op, OperationVariable[] variables) { + try { + Object obj = constructor.newInstance(); + return (OperationVariable[]) mtd.invoke(obj, path, op, variables); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + throw new IllegalStateException("Failed to invoke operation", e); + } + } + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/resources/application.yml b/basyx.submodelservice/basyx.submodelservice.component/src/main/resources/application.yml new file mode 100644 index 000000000..1b0d1dd30 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/resources/application.yml @@ -0,0 +1,12 @@ +basyx: + backend: InMemory + submodel: + file: + operation: + java: + sourcesPath: + classesPath: + additionalClasspath: [] + invokation: + defaultMapping: org.eclipse.digitaltwin.basyx.submodelservice.component.java.DefaultInvokableOperation + mappings: {} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/main/resources/banner.txt b/basyx.submodelservice/basyx.submodelservice.component/src/main/resources/banner.txt new file mode 100644 index 000000000..6e9db86fc --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/main/resources/banner.txt @@ -0,0 +1,9 @@ + ____ _____ + | _ \ / ____| + | |_) | __ _ | (___ _ _ __ __ + | _ < / _` | \___ \ | | | |\ \/ / + | |_) || (_| | ____) || |_| | > < + |____/ \__,_||_____/ \__, |/_/\_\ +======================== __/ |====== +Submodel Service |___/ +2.0.0-PREVIEW diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/DefaultOperationTest.java b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/DefaultOperationTest.java new file mode 100644 index 000000000..0fceff76b --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/DefaultOperationTest.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component; + +import org.eclipse.digitaltwin.aas4j.v3.model.OperationRequest; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationResult; +import org.eclipse.digitaltwin.basyx.submodelservice.component.util.TestOperationValues; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, properties = { + "basyx.operation.invokation.defaultMapping=org.eclipse.digitaltwin.basyx.submodelservice.component.ops.MockDefaultOperation", + "basyx.submodel.file=example/submodel.json" }) +@AutoConfigureMockMvc +public class DefaultOperationTest { + + @Autowired + private MockMvc mvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + public void testDefaultOperationInvoked() throws Exception { + OperationRequest request = TestOperationValues.requestForString("toBeIgored"); + String body = objectMapper.writeValueAsString(request); + OperationResult result = TestOperationValues.resultForString("default"); + String expected = objectMapper.writeValueAsString(result); + mvc.perform(MockMvcRequestBuilders.post("/submodel/submodel-elements/SquareOperation/invoke") + .contentType(MediaType.APPLICATION_JSON).content(body).accept(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().json(expected)); + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/DynamicClassLoadingTest.java b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/DynamicClassLoadingTest.java new file mode 100644 index 000000000..0e390a299 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/DynamicClassLoadingTest.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.eclipse.digitaltwin.basyx.submodelservice.component.java.DynamicJavaClassLoader; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +public class DynamicClassLoadingTest { + + private static final String SQUARE_OPERATION_CLS_NAME = "SquareOperation"; + private static final String HELLO_OPERATION_CLS_NAME = "HelloOperation"; + + @Test + public void testCompilationInvokableFactory() throws Exception { + DynamicJavaClassLoader loader = new DynamicJavaClassLoader(); + Path sources = Path.of("example", "sources"); + Path classes = Path.of("target", "dynamic-loading", "classes"); + List pathJars = List.of(Path.of("example/jars/HelloWorld.jar")); + + loader.compileClasses(sources, classes, pathJars); + + Assert.assertTrue(Files.exists(classes)); + try (URLClassLoader urlLoader = loader.loadClasses(classes, pathJars)) { + Class cls = urlLoader.loadClass(SQUARE_OPERATION_CLS_NAME); + Assert.assertEquals(SQUARE_OPERATION_CLS_NAME, cls.getName()); + cls = urlLoader.loadClass(HELLO_OPERATION_CLS_NAME); + Assert.assertEquals(HELLO_OPERATION_CLS_NAME, cls.getName()); + } catch (ClassNotFoundException e) { + Assert.fail("SquareOperation class could not be loaded"); + } + } + + @Test(expected = IllegalStateException.class) + public void testOnUnknownSrcFolderIllegalStateException() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IOException { + DynamicJavaClassLoader loader = new DynamicJavaClassLoader(); + Path sources = Path.of("example", "unknown"); + Path classes = Path.of("target", "dynamic-loading", "classes"); + List pathJars = List.of(Path.of("example/jars/HelloWorld.jar")); + loader.compileClasses(sources, classes, pathJars); + } +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/OperationMappingTest.java b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/OperationMappingTest.java new file mode 100644 index 000000000..112efb790 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/OperationMappingTest.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component; +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +import org.eclipse.digitaltwin.aas4j.v3.model.OperationRequest; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationResult; +import org.eclipse.digitaltwin.basyx.submodelservice.component.ops.MockOneArgMappingOperation; +import org.eclipse.digitaltwin.basyx.submodelservice.component.util.TestOperationValues; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, properties = { + "basyx.operation.invokation.mappings[BasicOperations.AddOperation]=org.eclipse.digitaltwin.basyx.submodelservice.component.ops.MockMappingOperation", + "basyx.operation.invokation.mappings.SquareOperation=org.eclipse.digitaltwin.basyx.submodelservice.component.ops.MockOneArgMappingOperation", + "basyx.submodel.file=example/submodel.json" }) +@AutoConfigureMockMvc +public class OperationMappingTest { + + @Autowired + private MockMvc mvc; + + @Autowired + private ObjectMapper mapper; + + @Test + public void testMappingOperationInvoked() throws Exception { + OperationRequest request = TestOperationValues.requestForInt(3); + OperationResult result = TestOperationValues.resultForString("BasicOperations", "AddOperation"); + String body = mapper.writeValueAsString(request); + String expected = mapper.writeValueAsString(result); + + mvc.perform(MockMvcRequestBuilders.post("/submodel/submodel-elements/BasicOperations.AddOperation/invoke") + .contentType(MediaType.APPLICATION_JSON).content(body).accept(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().json(expected)); + } + + @Test + public void testOneArgMethodsAreInvoked() throws Exception { + OperationRequest request = TestOperationValues.requestForInt(3); + OperationResult result = TestOperationValues.resultForString(MockOneArgMappingOperation.ONE_ARG); + String body = mapper.writeValueAsString(request); + String expected = mapper.writeValueAsString(result); + + mvc.perform(MockMvcRequestBuilders.post("/submodel/submodel-elements/SquareOperation/invoke") + .contentType(MediaType.APPLICATION_JSON).content(body).accept(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().json(expected)); + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/SubmodelServiceIntegrationTest.java b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/SubmodelServiceIntegrationTest.java new file mode 100644 index 000000000..225eec33d --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/SubmodelServiceIntegrationTest.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component; + +import org.eclipse.digitaltwin.aas4j.v3.model.OperationRequest; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationResult; +import org.eclipse.digitaltwin.basyx.submodelservice.component.util.TestOperationValues; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("integration") +public class SubmodelServiceIntegrationTest { + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void testSquareOperation() { + String url = "http://localhost:" + port + "/submodel/submodel-elements/SquareOperation/invoke"; + OperationRequest request = TestOperationValues.requestForInt(5); + + ResponseEntity response = restTemplate.postForEntity(url, request, OperationResult.class); + + OperationResult expected = TestOperationValues.resultForInt(25); + Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + Assert.assertEquals(expected, response.getBody()); + } + + @Test + public void testAddOperation() { + String url = "http://localhost:" + port + "/submodel/submodel-elements/BasicOperations.AddOperation/invoke"; + OperationRequest request = TestOperationValues.requestForInt(5,7); + + ResponseEntity response = restTemplate.postForEntity(url, request, OperationResult.class); + + OperationResult expected = TestOperationValues.resultForInt(12); + Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + Assert.assertEquals(expected, response.getBody()); + } + + @Test + public void testDefaultOperation() { + String url = "http://localhost:" + port + "/submodel/submodel-elements/BasicOperations.UnassignedOperation/invoke"; + OperationRequest request = TestOperationValues.requestForInt(5); + + ResponseEntity response = restTemplate.postForEntity(url, request, OperationResult.class); + // no operation handling assigned + Assert.assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/ops/MockDefaultOperation.java b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/ops/MockDefaultOperation.java new file mode 100644 index 000000000..99b3b9811 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/ops/MockDefaultOperation.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component.ops; + +import org.eclipse.digitaltwin.aas4j.v3.model.Operation; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.basyx.submodelservice.component.util.TestOperationValues; +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +public class MockDefaultOperation { + + public OperationVariable[] invoke(String path, Operation op, OperationVariable[] in) { + Property out = TestOperationValues.toStringProperty("default"); + OperationVariable outVar = TestOperationValues.toOperationVariable(out); + return new OperationVariable[] { outVar }; + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/ops/MockMappingOperation.java b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/ops/MockMappingOperation.java new file mode 100644 index 000000000..deb4f3a25 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/ops/MockMappingOperation.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component.ops; + +import java.util.Arrays; + +import org.eclipse.digitaltwin.aas4j.v3.model.Operation; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.basyx.submodelservice.component.util.TestOperationValues; + +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +public class MockMappingOperation { + + public OperationVariable[] invoke(String path, Operation op, OperationVariable[] in) { + return Arrays.stream(path.split("\\.")).map(TestOperationValues::toStringProperty).map(TestOperationValues::toOperationVariable).toArray(OperationVariable[]::new); + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/ops/MockOneArgMappingOperation.java b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/ops/MockOneArgMappingOperation.java new file mode 100644 index 000000000..e5150c560 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/ops/MockOneArgMappingOperation.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component.ops; + +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.basyx.submodelservice.component.util.TestOperationValues; +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +public class MockOneArgMappingOperation { + + public static final String ONE_ARG = "one_arg"; + + public OperationVariable[] invoke(OperationVariable[] in) { + Property oneArg = TestOperationValues.toStringProperty(ONE_ARG); + OperationVariable outVar = TestOperationValues.toOperationVariable(oneArg); + return new OperationVariable[] { outVar }; + } +} diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/util/TestOperationValues.java b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/util/TestOperationValues.java new file mode 100644 index 000000000..6bb2aa8be --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/component/util/TestOperationValues.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) + * + * 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.submodelservice.component.util; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationRequest; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationResult; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperationRequest; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperationResult; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperationVariable; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; +/** + * @author Gerhard Sonnenberg DFKI GmbH + */ +public class TestOperationValues { + + private TestOperationValues() { + } + + public static OperationRequest requestForInt(int... values) { + return new DefaultOperationRequest.Builder().inputArguments(intInputArguments(values)).build(); + } + + public static OperationRequest requestForString(String... values) { + return new DefaultOperationRequest.Builder().inputArguments(stringInputArguments(values)).build(); + } + + public static OperationResult resultForInt(int... values) { + return new DefaultOperationResult.Builder().outputArguments(intInputArguments(values)).build(); + } + + public static OperationResult resultForString(String... values) { + return new DefaultOperationResult.Builder().outputArguments(stringInputArguments(values)).build(); + } + + + private static List intInputArguments(int... values) { + return Arrays.stream(values).mapToObj(TestOperationValues::toIntProperty).map(TestOperationValues::toOperationVariable).collect(Collectors.toList()); + } + + private static List stringInputArguments(String... values) { + return Arrays.stream(values).map(TestOperationValues::toStringProperty).map(TestOperationValues::toOperationVariable).collect(Collectors.toList()); + } + + public static Property toStringProperty(String value) { + return new DefaultProperty.Builder().value(String.valueOf(value)).valueType(DataTypeDefXsd.STRING).build(); + } + + public static Property toIntProperty(int value) { + return new DefaultProperty.Builder().value(String.valueOf(value)).valueType(DataTypeDefXsd.INT).build(); + } + + public static OperationVariable toOperationVariable(Property prop) { + return new DefaultOperationVariable.Builder().value(prop).build(); + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/test/resources/application-integration.yml b/basyx.submodelservice/basyx.submodelservice.component/src/test/resources/application-integration.yml new file mode 100644 index 000000000..76736b92a --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/test/resources/application-integration.yml @@ -0,0 +1,13 @@ +basyx: + submodel: + file: example/submodel.json + operation: + java: + sourcesPath: example/sources + classesPath: target/integration-test/classes + additionalClasspath: + - example/jars/HelloWorld.jar + invokation: + mappings: + SquareOperation: SquareOperation + "BasicOperations.AddOperation": basic.ops.AddOperation \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice.component/src/test/resources/banner.txt b/basyx.submodelservice/basyx.submodelservice.component/src/test/resources/banner.txt new file mode 100644 index 000000000..19ed23d37 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice.component/src/test/resources/banner.txt @@ -0,0 +1,9 @@ + ____ _____ _______ _ + | _ \ / ____| |__ __| | | + | |_) | __ _ | (___ _ _ __ __ | | ___ ___ | |_ + | _ < / _` | \___ \ | | | |\ \/ / | | / _ \/ __|| __| + | |_) || (_| | ____) || |_| | > < | || __/\__ \| |_ + |____/ \__,_||_____/ \__, |/_/\_\ |_| \___||___/ \__| +======================== __/ |============================== +Submodel Service |___/ +2.0.0-PREVIEW diff --git a/basyx.submodelservice/pom.xml b/basyx.submodelservice/pom.xml index f5916f017..d8c2afb2f 100644 --- a/basyx.submodelservice/pom.xml +++ b/basyx.submodelservice/pom.xml @@ -20,6 +20,7 @@ basyx.submodelservice-backend-inmemory basyx.submodelservice-feature-mqtt basyx.submodelservice.example + basyx.submodelservice.component basyx.submodelservice-client