diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c29e6ce --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,49 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "nuget" + target-branch: "develop" + directory: "/source" + groups: + major-minor-patch: + applies-to: version-updates + update-types: + - "major" + - "minor" + - "patch" + schedule: + interval: "weekly" + cooldown: + default-days: 3 + + - package-ecosystem: "docker" + target-branch: "develop" + directory: "/source/AAS.TwinEngine.DataEngine" + groups: + major-minor-patch: + applies-to: version-updates + update-types: + - "major" + - "minor" + - "patch" + schedule: + interval: "daily" + + - package-ecosystem: "github-actions" + target-branch: "develop" + directory: "/" + groups: + major-minor-patch: + applies-to: version-updates + update-types: + - "major" + - "minor" + - "patch" + schedule: + interval: "weekly" + cooldown: + default-days: 3 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..9c05865 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,100 @@ +name: "CodeQL Advanced" + +on: + push: + branches: + - 'main' + - 'develop' + - 'release/**' + - 'hotfix/**' + pull_request: + branches: + - 'main' + - 'develop' + - 'release/**' + - 'hotfix/**' + schedule: + - cron: '30 19 * * 0' + +permissions: read-all + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: csharp + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..c57ba33 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,99 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable +# packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement +name: 'Dependency review' +on: + pull_request: + branches: + - 'main' + - 'develop' + - 'release/**' + - 'hotfix/**' + +# If using a dependency submission action in this workflow this permission will need to be set to: +# +# permissions: +# contents: write +# +# https://docs.github.com/en/enterprise-cloud@latest/code-security/supply-chain-security/understanding-your-software-supply-chain/using-the-dependency-submission-api +permissions: + contents: read + # Write permissions for pull-requests are required for using the `comment-summary-in-pr` option, comment out if you aren't using this option + pull-requests: write + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout repository' + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - name: 'Dependency Review' + uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 + # Commonly enabled options, see https://github.com/actions/dependency-review-action#configuration-options for all available options. + with: + retry-on-snapshot-warnings: true + retry-on-snapshot-warnings-timeout: 60 + warn-on-openssf-scorecard-level: 5 + comment-summary-in-pr: always + allow-dependencies-licenses: | + pkg:nuget/AasCore.Aas3_0, + pkg:nuget/AasCore.Aas3.Package + allow-licenses: | + Apache-1.0, + Apache-1.1, + Apache-2.0, + BSL-1.0, + BSD-1-Clause, + BSD-2-Clause, + BSD-2-Clause-FreeBSD, + BSD-2-Clause-NetBSD, + BSD-3-Clause, + BSD-3-Clause-Clear, + BSD-3-Clause-No-Nuclear-License, + BSD-3-Clause-No-Nuclear-License-2014, + BSD-3-Clause-No-Nuclear-Warranty, + BSD-3-Clause-Open-MPI, + BSD-4-Clause, + BSD-Protection, + BSD-Source-Code, + BSD-3-Clause-Attribution, + 0BSD, + BSD-2-Clause-Patent, + BSD-4-Clause-UC, + MIT-CMU, + CC-BY-3.0, + CC-BY-SA-1.0, + CC-BY-SA-2.0, + CC-BY-SA-2.5, + CC-BY-SA-3.0, + CC-BY-SA-4.0, + CC0-1.0, + WTFPL, + MIT-enna, + MIT-feh, + ISC, + JSON, + BSD-3-Clause-LBNL, + MITNFA, + MIT, + MIT-0, + UPL-1.0, + NCSA, + X11, + Xerox, + BlueOak-1.0.0, + CC-BY-4.0, + MS-PL, + PostgreSQL, + Python-2.0, + SSPL-1.0, + OFL-1.1, + Unlicense, + Unicode-DFS-2016, + Unicode-3.0 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..113c32f --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,125 @@ +name: Docker + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + workflow_dispatch: + push: + branches: + - 'develop' + - 'release/**' + - 'hotfix/**' + # Publish semver tags as releases. + tags: [ 'v**' ] + pull_request: + branches: + - 'main' + - 'develop' + - 'release/**' + - 'hotfix/**' + +permissions: + contents: read + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository_owner }}/dataengine + DOCKERFILE_PATH: source/AAS.TwinEngine.DataEngine/Dockerfile + BUILD_CONTEXT: source + + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + latest=false + tags: | + type=semver,pattern={{raw}} + type=raw,value=develop-{{sha}},enable=${{startsWith(github.ref, 'refs/heads/develop')}} + type=raw,value=rc-{{branch}}-{{sha}},enable=${{startsWith(github.ref, 'refs/heads/release/')}} + type=raw,value={{branch}}-{{sha}},enable=${{startsWith(github.ref, 'refs/heads/hotfix/')}} + type=ref,event=pr + type=raw,value=manual-{{branch}}-{{sha}},enable=${{github.event_name == 'workflow_dispatch'}} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + with: + sbom: true + provenance: mode=max + context: ${{ env.BUILD_CONTEXT }} + file: ${{ env.DOCKERFILE_PATH }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + + # Extract the pure application SBOM from the artifact stage, we want to handle it separately from the container SBOM + # This automaticaly re-uses the previously generated stage from cache, so we get the exact sbom from previous build step + - name: Export Application SBOM from artifact stage + if: ${{ github.event_name != 'pull_request' }} + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + with: + context: ${{ env.BUILD_CONTEXT }} + file: ${{ env.DOCKERFILE_PATH }} + target: app-sbom-artifact + push: false + outputs: type=local,dest=sbom-output + + # Generate container SBOM. + - name: Run Trivy in GitHub SBOM mode to generate CycloneDX SBOM for container + if: ${{ github.event_name != 'pull_request' }} + uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1 + with: + scan-type: 'image' + format: 'cyclonedx' + output: 'sbom-output/sbom_container.cyclonedx.json' + image-ref: ${{ steps.meta.outputs.tags }} + skip-dirs: '/App' # Skip the /app directory as we handle the content of the application in a seperate SBOM for easier vulnerability management and because trivy misses important fields + + - name: Upload trivy/container AND application SBOMs as a Github artifact + if: ${{ github.event_name != 'pull_request' }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: sbom + path: '${{ github.workspace }}/sbom-output/' diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..1af6d85 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,141 @@ +name: .NET + +on: + workflow_dispatch: + pull_request: + branches: + - 'main' + - 'develop' + - 'release/**' + - 'hotfix/**' + push: + branches: + - 'main' + - 'develop' + - 'release/**' + - 'hotfix/**' + +permissions: + contents: read + +env: + BUILD_CONFIGURATION: "Release" + SOLUTION_PATH: source/AAS.TwinEngine.DataEngine.sln + TEST_PROJECT: source/AAS.TwinEngine.DataEngine.UnitTests/AAS.TwinEngine.DataEngine.UnitTests.csproj + MODULE_TEST_PROJECT: source/AAS.TwinEngine.DataEngine.ModuleTests/AAS.TwinEngine.DataEngine.ModuleTests.csproj + +jobs: + + build-test: + name: Build & Test + runs-on: ubuntu-latest + + permissions: + contents: read + checks: write + pull-requests: write + + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Setup .NET + uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1 + with: + dotnet-version: "8.0.x" + + - name: Restore + run: dotnet restore ${{ env.SOLUTION_PATH }} --locked-mode + + - name: Build + run: dotnet build ${{ env.SOLUTION_PATH }} --configuration ${{ env.BUILD_CONFIGURATION }} --no-restore + + - name: Run Unit Tests + run: dotnet test ${{ env.TEST_PROJECT }} --configuration Release --no-build --logger "trx;LogFileName=unit_test_results.trx" --collect:"XPlat Code Coverage" --settings source/coverlet.runsettings + + - name: Run Module Tests + run: dotnet test ${{ env.MODULE_TEST_PROJECT }} --configuration Release --no-build --logger "trx;LogFileName=module_test_results.trx" --collect:"XPlat Code Coverage" --settings source/coverlet.runsettings + + - name: Publish Test Results + uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.0 + id: test-results + if: github.event.pull_request.head.repo.fork == false + with: + name: Test Results (Unit & Module) + path: "**/*_test_results.trx" + reporter: dotnet-trx + + - name: Combine Reports (Test Results) + if: github.event.pull_request.head.repo.fork == false + run: | + echo "# Test & Coverage Report" > results.md + echo "" >> results.md + echo "## Test Results Summary" >> results.md + echo "" >> results.md + echo "| Metric | Count |" >> results.md + echo "|--------|-------|" >> results.md + echo "| ✅ Passed | ${{ steps.test-results.outputs.passed }} |" >> results.md + echo "| ❌ Failed | ${{ steps.test-results.outputs.failed }} |" >> results.md + echo "| ⏭️ Skipped | ${{ steps.test-results.outputs.skipped }} |" >> results.md + echo "" >> results.md + echo "[View Detailed Test Results](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> results.md + echo "" >> results.md + echo "---" >> results.md + echo "" >> results.md + + - name: Code Coverage Report - Unit Tests + if: github.event.pull_request.head.repo.fork == false + uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0 + with: + filename: "source/AAS.TwinEngine.DataEngine.UnitTests/TestResults/*/coverage.cobertura.xml" + badge: false + fail_below_min: true + format: markdown + hide_branch_rate: false + hide_complexity: false + indicators: true + output: both + thresholds: "80 80" + + - name: Combine Reports (Unit Test Coverage Results) + if: github.event.pull_request.head.repo.fork == false + run: | + echo "## Code Coverage" >> results.md + echo "" >> results.md + echo "### Unit Tests Coverage" >> results.md + cat code-coverage-results.md >> results.md + echo "" >> results.md + + - name: Code Coverage Report - Module Tests + if: github.event.pull_request.head.repo.fork == false + uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0 + with: + filename: "source/AAS.TwinEngine.DataEngine.ModuleTests/TestResults/*/coverage.cobertura.xml" + badge: false + fail_below_min: false + format: markdown + hide_branch_rate: false + hide_complexity: false + indicators: true + output: both + + - name: Combine Reports (Module Test Coverage Results) + if: github.event.pull_request.head.repo.fork == false + run: | + echo "### Module Tests Coverage" >> results.md + cat code-coverage-results.md >> results.md + + + - name: Add PR Comment + if: github.event.pull_request.head.repo.fork == false + uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2.9.0 + with: + recreate: true + path: results.md + + - name: Upload TRX as artifact (Fork PR fallback) + if: github.event.pull_request.head.repo.fork == true + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: test-results + path: "**/*_test_results.trx" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..0d586b6 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,78 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '26 2 * * *' + push: + branches: [ "main" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. + if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: false + + # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore + # file_mode: git + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard (optional). + # Commenting out will disable upload of results to your repo's Code Scanning dashboard + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6 + with: + sarif_file: results.sarif diff --git a/README.md b/README.md index 3dc74ed..ab6f7c3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,101 @@ # DataEngine -**DataEngine** is a .NET-based service that dynamically generates complete **Asset Administration Shell (AAS)** submodels by combining standardized templates with real-time data. -It integrates with **Eclipse BaSyx** and follows **IDTA specifications** to ensure interoperability. -When a submodel is requested, DataEngine retrieves its template, queries the **Plugin** for semantic ID values, and populates the structure automatically. -It supports nested and hierarchical data models, providing ready-to-use submodels for visualization or API consumption. -In short, DataEngine acts as the **core orchestration layer** that transforms static AAS templates into live digital representations. +[![Made by M&M Software](https://img.shields.io/badge/Made_by_M%26M_Software-364955?style=flat-square)](https://www.mm-software.com/) +[![Apache License](https://img.shields.io/badge/License-Apache-364955.svg?style=flat-square)](https://www.apache.org/licenses/) +[![.NET 8.0](https://img.shields.io/badge/.NET-8.0-512BD4)](https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-10.0&WT.mc_id=dotnet-35129-website) + +## Overview + +## DataEngine Overview + +**DataEngine** is an API service that dynamically generates complete **Asset Administration Shell (AAS) submodels** by combining standardized AAS templates with provided data from database via plugins. + +It acts as the **core orchestration layer** in the TwinEngine ecosystem, transforming static AAS templates into **live, ready-to-consume AAS representations** through a **plugin-based value resolution mechanism**. + +DataEngine integrates seamlessly with **Eclipse BaSyx** components and is also capable of orchestrating other **open-source AAS-related components**, enabling a **flexible, vendor-neutral AAS infrastructure**. + +--- + +## Features Overview + +- **Template-based Dynamic AAS and Submodel Generation** + Dynamically builds complete AAS structures using standardized, reusable templates (containing semantic IDs without values). + - For more info : [Template-driven submodel generation](https://github.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/wiki/Feature-Overview#template-driven-submodel-generation) + +- **Schema-driven Plugin Integration** + Decouples data access through external Plugin APIs using JSON Schema contracts for type-safe communication. + - For more info : [Plugin-based value resolution](https://github.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/wiki/Feature-Overview#plugin-based-value-resolution) + +- **IDTA-aligned AAS REST Endpoints** + Multiple API endpoints that align with official IDTA AAS specifications, supporting shell descriptor, submodel, and submodel element operations for seamless interoperability. + - For more info : [Idta-aligned-endpoints](https://github.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/wiki/Feature-Overview#idta-aligned-endpoints) + +- **Multi-Plugin Orchestration** + DataEngine supports orchestration across multiple Plugin APIs to resolve runtime data simultaneously. + - For more info : [Multi-plugin orchestration](https://github.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/wiki/Feature-Overview#multi-plugin-orchestration) + +- **Nested and Hierarchical Submodel Support** + Handles complex Submodels with deeply nested and structured SubmodelElements. + - For more info : [Hierarchical & nested models](https://github.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/wiki/Feature-Overview#hierarchical--nested-models) + +- **Comprehensive SubmodelElement Type Support** + Supports a broad range of SubmodelElement types with semantic preservation during population. + - For more info : [Support for SubmodelElement types](https://github.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/wiki/Feature-Overview#support-for-submodelelement-types) + +## DataEngine - AAS Generation Flow + +```mermaid +sequenceDiagram + actor A as Client + participant B as DataEngine + participant C as Template Repository + participant D as Plugins + + A->>B: GET /submodels/{submodelIdentifier} + B->>C: GET / Submodel Templates + C-->>B: Submodel Template (Semantic IDs) + B->>B: Extract semantic IDs + B->>D: POST /data/{submodelId} + D-->D: Resolve semantic IDs + D-->>B: Semantic Id with Values + B->>B: Populate Template + B-->>A: Filled submodel +``` + +The DataEngine transforms **static AAS templates** into **live digital representations**. + +### Step-by-Step Process + +When a client requests AAS data (shell descriptor, submodel, or submodel element): + +1. **Fetch Template** - DataEngine retrieves the required AAS/Submodel template from the AAS Template Registry +2. **Extract Semantic IDs** - Identifies all semantic IDs within the template that need values +3. **Request Data via Schema** - Sends a JSON Schema to the Plugin API describing the structure and semantic IDs needed +4. **Receive Values** - Plugin queries its database and responds with populated values for the requested semantic IDs +5. **Populate Template** - DataEngine injects the received values into the template structure +6. **Return AAS submodel Response** - Responds to the client with a complete, ready-to-use AAS structure + + +## **Quick Start Guide** + +### Running the Setup With DPP Plugin + +1. **Clone or extract this repository:** + ```bash + git clone https://github.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine.git + cd AAS.TwinEngine.DataEngine/example + ``` + +2. **Start all services:** + ```bash + docker-compose up -d + ``` + +3. **Access the Web UI:** + Open your browser and navigate to: + ``` + http://localhost:8080/aas-ui/ + ``` +- For more info : [TwinEngine Demonstrator Setup](https://github.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/blob/develop/example/README.md) +--- diff --git a/apiCollection/environments/develop.bru b/apiCollection/environments/develop.bru deleted file mode 100644 index 2b22af1..0000000 --- a/apiCollection/environments/develop.bru +++ /dev/null @@ -1,3 +0,0 @@ -vars { - DataEngineBaseUrl: https://twinengine-dataengine-dev-euw-ca.icystone-61e99dc5.westeurope.azurecontainerapps.io -} diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..6776b04 --- /dev/null +++ b/example/README.md @@ -0,0 +1,172 @@ +# TwinEngine Demonstrator Setup + +## Overview + +This folder provides a complete, containerized setup to demonstrate how **TwinEngine.DataEngine** can be integrated and run locally. It creates a fully functional environment for managing Asset Administration Shells (AAS), submodels, and related digital asset components using Docker Compose. + +The setup includes a complete tech stack with services for AAS registry, repository, submodel management, data persistence, UI access, and a plugin system—all orchestrated through Docker containers on a shared network. + +## Included Submodel Templates + +This example includes 5 standardized submodel templates from the **Digital Product Passport for Industry 4.0**: + +- **Nameplate** +- **ContactInformation** +- **TechnicalData** +- **CarbonFootprint** +- **HandoverDocumentation** + +## Quick Start + +### Prerequisites + +Before running the demonstrator, ensure you have installed: + +- **Docker** (v20.10+) — [Install Docker](https://docs.docker.com/get-docker/) +- **Docker Compose** (v1.29+) — Usually included with Docker Desktop +- **Available Ports** — The following ports must be available on your machine: + - `8080` — Main API Gateway (nginx) + - `8081` - PGAdmin + +### Running the Setup + +1. **Clone or extract this repository:** + ```bash + git clone https://github.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine.git + cd AAS.TwinEngine.DataEngine + ``` +2. **Go Inside example Folder** + +```bash +cd AAS.TwinEngine.DataEngine\example +``` + +2. **Start all services:** + ```bash + docker-compose up -d + ``` + +3. **Access the Web UI:** + Open your browser and navigate to: + ``` + http://localhost:8080/aas-ui/ + ``` + +4. **Stop all services:** + ```bash + docker-compose down + ``` + +## Architecture & Services + +The docker-compose setup includes the following services, all running on a shared `twinengine-network`: + +### Core Services + +| Service | Port | Image | Purpose | +|---------|------|-------|----------| +| **nginx** | 8080 | `nginx:trixie-perl` | API Gateway & Web UI proxy | +| **twinengine-dataengine** | - | `ghcr.io/aas-twinengine/dataengine:1.0.0` | Main TwinEngine DataEngine service | +| **template-repository** | - | `eclipsebasyx/aas-environment:2.0.0-SNAPSHOT` | AAS Environment & Submodel repository | +| **aas-template-registry** | - | `eclipsebasyx/aas-registry-log-mongodb:2.0.0-SNAPSHOT` | AAS Shell Descriptor Registry | +| **sm-template-registry** | - | `eclipsebasyx/submodel-registry-log-mongodb:2.0.0-SNAPSHOT` | Submodel Descriptor Registry | +| **dpp-plugin** | - | `ghcr.io/aas-twinengine/plugindpp:1.0.0` | Digital Product Passport Plugin | +| **aas-web-ui** | — | `eclipsebasyx/aas-gui:SNAPSHOT` | Web User Interface (served via nginx) | + +### Infrastructure Services + +| Service | Port | Image | Purpose | +|---------|------|-------|----------| +| **postgres** | - | `postgres:16-alpine` | Relational database for plugin data | +| **pgadmin** | 8081 | `dpage/pgadmin4:snapshot` | Web UI for managing PostgreSQL database | +| **mongo** | - | `mongo:6.0` | NoSQL database for registry metadata | + +## Creating/Changing Your AAS-Data + +### Using PGAdmin + +PGAdmin provides a web-based interface to manage the PostgreSQL database without writing SQL queries. + +**Access PGAdmin:** +1. Navigate to `http://localhost:8081` +2. Login with: + - **Email:** admin@example.com + - **Password:** admin + +**Connect to PostgreSQL Server:** +1. In PGAdmin, click **"Add New Server"** +2. Fill in the connection details: + - **Name:** twinengine + - **Host name:** postgres + - **Port:** 5432 + - **Username:** postgres + - **Password:** admin + - **Database:** twinengine +3. Click **"Save"** + +**Browse and Modify Data:** +- In the left sidebar, navigate to: **Servers → twinengine → Databases → twinengine → Schemas → public → Tables** +- Right-click any table and select **"View/Edit Data"** to manage records +- Create new records or modify existing ones directly through the UI + +**How changes affect the Plugin:** +- Updates to application data (e.g., shell records, submodels, submodel element values) are reflected in what the Plugin serves. +- Submodel and shell templates are managed by BaSyx services and are not modified via PostgreSQL. + +-- + +## Additional Notes + +### PostgreSQL Database (Plugin) + +If desired, you can edit credentials in `docker-compose.yml`: +```yaml +POSTGRES_PASSWORD: admin +``` + +Update plugin connection string to match. Edit `example/postgres/init.sql` for custom schema/data. + +**Using an External Database:** +To use your own database instead: +1. Change `RelationalDatabaseConfiguration__ConnectionString` in the plugin service environment variables +2. Remove the postgres container from `docker-compose.yml` + +**Database Initialization:** +The initial database script is located in `postgres/init.sql`. Modify this file as needed for your requirements. + +**Security and Production Notice** + +Change all default passwords before any use beyond local development. Default credentials (postgres: admin) are for **development** only. + +In production, hosting and managing the PostgreSQL database is the customer's responsibility, not the DataEngine's. Use a managed or self-hosted, production-grade PostgreSQL instance and configure the plugin connection string accordingly. + + +### Port Changes + +Modify port mappings in `docker-compose.yml`. Update corresponding environment variables in affected services. + +### Security Note + +**Change default passwords before any use beyond local development.** Default credentials (postgres: admin) are for development only. + +In production: use a secure API gateway (Azure API Management, AWS API Gateway, Kong), and manage database security (encryption, access control, backups) is a customer responsibility. +*Do not use this Docker Compose configuration in production.* +--- + +## Troubleshooting + +**UI not loading:** `docker-compose logs nginx` - Verify ports 8080-8086 are available. + +**Port conflicts:** `netstat -ano | findstr :8080` (Windows) to find conflicts. Change ports in `docker-compose.yml`. + +**Startup issues:** Run `docker-compose pull` followed by `docker-compose up -d --force-recreate` + +**Database errors:** Check `docker-compose ps` for health status. Verify connection strings match credentials. + +**PGAdmin not accessible:** Verify the postgres service is healthy with `docker-compose ps`. Check port mappings are correctly configured. + + + +## Additional Resources + +- [TwinEngine Documentation](https://github.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/wiki) diff --git a/example/aas/CarbonFootprint.xml b/example/aas/CarbonFootprint.xml new file mode 100644 index 0000000..a99092d --- /dev/null +++ b/example/aas/CarbonFootprint.xml @@ -0,0 +1,2597 @@ + + + + + AasTemplate + https://admin-shell.io/idta/aas/CarbonFootprintAAS/1/0 + + Type + https://admin-shell.io/idta/asset/CarbonFootprintAAS/1/0 + + /2b6b0bd3.png + image/png + + Type + + + + ModelReference + + + Submodel + https://admin-shell.io/idta/SubmodelTemplate/CarbonFootprint/1/0 + + + + + + + + + CarbonFootprint + + + en + The Submodel provides the means to access the Carbon Footprint of the asset. + + + + 1 + 0 + https://admin-shell.io/IDTA 02023-1-0 + + https://admin-shell.io/idta/SubmodelTemplate/CarbonFootprint/1/0 + Template + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/CarbonFootprint/1/0 + + + + + + ProductCarbonFootprints + + + de + Produkt CO2-Fußabdruck + + + en + Product carbon footprint + + + + + en + Balance of greenhouse gas emissions along the entire life cycle of a product in a defined application and in relation to a defined unit of use. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ProductCarbonFootprints/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + true + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ProductCarbonFootprint/1/0 + + + + SubmodelElementCollection + + + ProductCarbonFootprint + + + de + Produkt CO2-Fußabdruck + + + en + Product carbon footprint + + + + + en + Balance of greenhouse gas emissions along the entire life cycle of a product in a defined application and in relation to a defined unit of use. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ProductCarbonFootprint/1/0 + + + + + + PcfCalculationMethods + + + de + Folgenabschätzungsmethoden + + + en + impact assessment methods + + + + + de + Normen, Standards, Verfahren zur Ermittlung der Treibhausgas-Emissionen eines Produkts + + + en + Standards, methods for determining the greenhouse gas emissions of a product. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/PcfCalculationMethods/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + true + + ExternalReference + + + GlobalReference + 0173-1#02-ABG854#003 + + + + Property + xs:string + + + PARAMETER + PcfCalculationMethod + + + de + Folgenabschätzungsmethode / Berechnungsmethode + + + en + impact assessment method / calculation method + + + + + en + Standard, method for determining the greenhouse gas emissions of a product. + + + de + Norm, Standard, Verfahren zur Ermittlung der Treibhausgas-Emissionen eines Produkts + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABG854#003 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:string + + + + + + LifeCyclePhases + + + de + Lebenszyklusphasen + + + en + life cycle phases + + + + + en + List of life cycle stages of the product according to the quantification requirements of the standard to which the PCF carbon footprint statement refers + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/LifeCyclePhases/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + true + + ExternalReference + + + GlobalReference + 0173-1#02-ABG858#003 + + + + Property + xs:string + + + PARAMETER + LifeCyclePhase + + + de + Lebenszyklusphase + + + en + life cycle phase + + + + + en + Life cycle stages of the product according to the quantification requirements of the standard to which the PCF carbon footprint statement refers + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABG858#003 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:string + + + + + + PARAMETER + PcfCO2eq + + + de + CO2-Äquivalent + + + en + CO2 equivalent + + + + + en + Sum of all greenhouse gas emissions of a product according to the quantification requirements of the standard. + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABG855#003 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:decimal + + + PARAMETER + ReferenceImpactUnitForCalculation + + + de + Referenzeinheit für die Berechnung + + + en + Reference value for calculation + + + + + en + Quantity unit of the product to which the PCF information on the CO2 footprint refers. + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABG856#003 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:string + + + PARAMETER + QuantityOfMeasureForCalculation + + + de + Mengenangabe für die Berechnung + + + en + quantity of measure for calculation + + + + + en + provides the quantity number of pieces or mass or volume to compute the impact of climate change or product carbon footprint. + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABG857#003 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:double + + + PARAMETER + PublicationDate + + + de + Veröffentlichungsdatum + + + en + Publication date + + + + + en + The UTC timestamp on which a Product Carbon Footprint (PCF) - a calculation of a product's total greenhouse gas emissions - was created and published + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/PublicationDate/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:dateTime + + + PARAMETER + ExpirationDate + + + de + Ablaufdatum + + + en + Expiration date + + + + + en + End date up to which a study or data collection for calculating an ecological footprint is considered current and valid before an update or new calculation is required. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ExpirationDate/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:dateTime + + + PARAMETER + ExplanatoryStatement + + + de + Erklärung + + + en + Explanatory statement + + + + + en + Explanation required or provided to ensure that a footprint communication can be properly understood by a purchaser, potential purchaser, or user of the product. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ExplanatoryStatement/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + application/pdf + + + GoodsHandoverAddress + + + de + Warenübergabeadresse + + + en + goods address hand-over + + + + + en + Indicates the hand-over address of the goods transport + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/AddressInformation + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/smt-dropin/smt-dropin-use/1/0 + + + + + ExternalReference + + + GlobalReference + 0112/2///61360_7#AAS002#001 + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAQ837#008 + + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + + + + + + ProductOrSectorSpecificCarbonFootprints + + + en + Product Or Sector Specific Carbon Footprints + + + + + en + Product Carbon Footprints, which is determined in accordance with sector or product group-specific rules or guidelines and covers the life cycle or parts of a product life cycle. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificCarbonFootprints/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + true + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificCarbonFootprint/1/0 + + + + SubmodelElementCollection + + + ProductOrSectorSpecificCarbonFootprint + + + de + Produkt CO2-Fußabdruck + + + en + Product carbon footprint + + + + + en + Product Carbon Footprint, which is determined in accordance with sector or product group-specific rules or guidelines and covers the life cycle or parts of a product life cycle. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificCarbonFootprint/1/0 + + + + + + PcfCalculationMethods + + + de + Folgenabschätzungsmethoden + + + en + impact assessment methods + + + + + de + Normen, Standards, Verfahren zur Ermittlung der Treibhausgas-Emissionen eines Produkts + + + en + Standards, methods for determining the greenhouse gas emissions of a product. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/PcfCalculationMethods/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + true + + ExternalReference + + + GlobalReference + 0173-1#02-ABG854#003 + + + + Property + xs:string + + + PARAMETER + PcfCalculationMethod + + + de + Folgenabschätzungsmethode / Berechnungsmethode + + + en + impact assessment method / calculation method + + + + + en + Standard, method for determining the greenhouse gas emissions of a product. + + + de + Norm, Standard, Verfahren zur Ermittlung der Treibhausgas-Emissionen eines Produkts + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABG854#003 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:string + + + + + + ProductOrSectorSpecificRule + + + de + Produktspezifische oder sektorspezifische Regel + + + en + Product or Sector Specific Rule + + + + + de + Beinhaltet weiterführende Informationen zur produktspezifischen oder sektorspezifischen Regel, welche zur Berechnung des CO2-Fußabdrucks eingesetzt wird. + + + en + Contains further information on the product-specific or sector-specific rule used to calculate the carbon footprint. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificRule/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + + PcfRuleOperator + + + de + Herausgeber der PCF Berechnungsmethode + + + en + Operator of the PCF calculation method + + + + + de + Einrichtung, welche spezifische Anweisungen und Methoden zur Berechnung und Überwachung des CO2-Fußabdrucks eines Produkts oder Sektors definiert und umsetzt. + + + en + Organization that defines and implements specific instructions and methods for calculating and monitoring the carbon footprint of a product or sector. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificRule/Operator/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:string + + + PcfRuleName + + + de + Name der PCF Berechnungsmethode + + + en + Name of the PCF calculation method + + + + + de + Folgenabschätzungsmethode / Berechnungsmethode + + + en + Standard, method for determining the greenhouse gas emissions of a product + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificRule/Name/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:string + + + PcfRuleVersion + + + de + Version der PCF Berechnungsmethode + + + en + Version of the PCF calculation method + + + + + de + Spezifische Ausgabe oder Revision der Regel, die zur Berechnung des CO2-Fußabdrucks eines Produkts verwendet wird. + + + en + Specific version or revision of the rule used to calculate the carbon footprint of a product. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificRule/Version/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + + + PcfRuleOnlineReference + + + de + Online Referenz zur PCF Berechnungsmethode + + + en + Online reference to the PCF calculation method + + + + + de + Online-Referenz zur PCF-Berechnungsmethodik, die detaillierte Anweisungen und Richtlinien zur Berechnung des CO2-Fußabdrucks eines Produkts bereitstellt. + + + en + Online PCF calculation methodology reference that provides detailed instructions and guidelines for calculating a product's carbon footprint. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificRule/OnlineReference/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + application/pdf + + + + + ExternalPcfApi + + + de + Externe API für PCF Informationen + + + en + External API for PCF information + + + + + de + Ein externer Dienst, der über eine Schnittstelle Informationen zum CO2-Fußabdruck bereitstellt und den Abruf dieser Daten auf Abruf ermöglicht. + + + en + An external service that provides carbon footprint information via an interface, allowing on-demand retrieval of this data when required. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ExternalPcfApi/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + + PcfApiEndpoint + + + de + Endpunkt + + + en + Endpoint + + + + + de + Spezifische URL oder Adresse, über die Daten zur Berechnung des CO2-Fußabdrucks eines Produkts von externen Quellen abgerufen werden können. + + + en + Specific URL or address that can be used to retrieve data from external sources to calculate the carbon footprint of a product. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ExternalPcfApi/Endpoint/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:anyURI + + + PcfApiQuery + + + de + Abfrage + + + en + Query + + + + + de + Spezifische Abfrage, über die Daten zur Berechnung des CO2-Fußabdrucks eines Produkts von externen Quellen abgerufen werden können. + + + en + Specific query that can be used to retrieve data from external sources to calculate the carbon footprint of a product. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/ExternalPcfApi/Query/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:string + + + + + PcfInformation + + + de + Ein Abschnitt, in dem weitere Inhalte entsprechend der Berechnungsmethode zum Product Carbon Footprint aufgeführt werden. + + + en + A section in which further content is listed according to the calculation method for the Product Carbon Footprint. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/CarbonFootprint/PcfInformation/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + + + + + + + + de + C02 Footprint + + + en + Carbon Footprint + + + + + + + PcfApiEndpoint + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/ExternalPcfApi/Endpoint/1/0 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Endpoint + + + de + Endpunkt + + + STRING + + + de + Spezifische URL oder Adresse, über die Daten zur Berechnung des CO2-Fußabdrucks eines Produkts von externen Quellen abgerufen werden können. + + + en + Specific URL or address that can be used to retrieve data from external sources to calculate the carbon footprint of a product. + + + + + + + + + PcfRuleVersion + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificRule/Version/1/0 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Version of the PCF calculation method + + + de + Version der PCF Berechnungsmethode + + + + + en + Version + + + de + Version + + + STRING + + + de + Spezifische Ausgabe oder Revision der Regel, die zur Berechnung des CO2-Fußabdrucks eines Produkts verwendet wird. + + + en + Specific version or revision of the rule used to calculate the carbon footprint of a product. + + + 2.0 + + + + + + + PcfCalculationMethod + 0173-1#02-ABG854#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + impact assessment method / calculation method + + + de + Folgenabschätzungsmethode / Berechnungsmethode + + + STRING + + + de + Norm, Standard, Verfahren zur Ermittlung der Treibhausgas-Emissionen eines Produkts + + + en + standard, method for determining the greenhouse gas emissions of a product + + + + + + EN 15804 + + ExternalReference + + + GlobalReference + 0173-1#07-ABU223#003 + + + + + + GHG Protocol + + ExternalReference + + + GlobalReference + 0173-1#07-ABU221#003 + + + + + + IEC TS 63058 + + ExternalReference + + + GlobalReference + 0173-1#07-ABU222#003 + + + + + + IEC 63366 + + ExternalReference + + + GlobalReference + 0173-1#07-ACA792#002 + + + + + + ISO 14040, ISO 14044 + + ExternalReference + + + GlobalReference + 0173-1#07-ABV505#003 + + + + + + ISO 14067 + + ExternalReference + + + GlobalReference + 0173-1#07-ABU218#003 + + + + + + PEP Ecopassport + + ExternalReference + + + GlobalReference + 0173-1#07-ABU220#003 + + + + + + PACT v1.0.1 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC004#001 + + + + + + PACT v2.0.0 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC003#001 + + + + + + PACT v3.0.0 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC012#001 + + + + + + TFS v2 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC005#001 + + + + + + TFS v3 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC010#001 + + + + + + Catena-X v1 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC007#001 + + + + + + Catena-X v2 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC006#001 + + + + + + Catena-X v3 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC011#001 + + + + + + BS PAS 2050 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC008#001 + + + + + + IEC 63372 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC019#001 + + + + + + + + + + + + + PublicationDate + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/PublicationDate/1/0 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + de + Veröffentlichungsdatum + + + en + Publication date + + + + + en + EMPTY + + + TIMESTAMP + + + de + Der UTC Zeitstempel, an dem ein Product Carbon Footprint (PCF) – eine Berechnung der gesamten Treibhausgasemissionen eines Produkts – erstellt und veröffentlicht wurde + + + en + The UTC timestamp on which a Product Carbon Footprint (PCF) - a calculation of a product's total greenhouse gas emissions - was created and published + + + + + + + + + ExpirationDate + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/ExpirationDate/1/0 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + de + Ablaufdatum + + + en + Expiration Date + + + TIMESTAMP + + + de + Enddatum, bis zu dem eine Studie oder Datenerhebung zur Berechnung eines ökologischen Fußabdrucks als aktuell und gültig betrachtet wird, bevor eine Aktualisierung oder neue Berechnung erforderlich ist. + + + en + End date up to which a study or data collection for calculating an ecological footprint is considered current and valid before an update or new calculation is required. + + + + + + + + + PcfRuleOperator + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificRule/Operator/1/0 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Publisher of the PCF calculation method + + + de + Herausgeber der PCF Berechnungsmethode + + + STRING + + + de + Einrichtung, welche spezifische Anweisungen und Methoden zur Berechnung und Überwachung des CO2-Fußabdrucks eines Produkts oder Sektors definiert und umsetzt. + + + en + Organization that defines and implements specific instructions and methods for calculating and monitoring the carbon footprint of a product or sector. + + + + + + + + + ProductOrSectorSpecificRule + + + de + Produktspezifische oder sektorspezifische Regel + + + en + Product or Sector Specific Rule + + + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificRule/1/0 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Product or Sector Specific Rule + + + de + Produktspezifische oder sektorspezifische Regel + + + + + de + Beinhaltet weiterführende Informationen zur produktspezifischen oder sektorspezifischen Regel, welche zur Berechnung des CO2-Fußabdrucks eingesetzt wird. + + + en + Contains further information on the product-specific or sector-specific rule used to calculate the carbon footprint. + + + + + + + + + ReferenceImpactUnitForCalculation + 0173-1#02-ABG856#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Reference value for calculation + + + de + Referenzeinheit für die Berechnung + + + STRING + + + en + Quantity unit of the product to which the PCF information on the CO2 footprint refers + + + de + Mengeneinheit des Produktes, auf das sich die PCF-Angabe zum CO2-Fußabdruck bezieht + + + + + + g + + ExternalReference + + + GlobalReference + 0173-1#07-ABZ596#003 + + + + + + kg + + ExternalReference + + + GlobalReference + 0173-1#07-ABZ597#003 + + + + + + t + + ExternalReference + + + GlobalReference + 0173-1#07-ABZ598#003 + + + + + + ml + + ExternalReference + + + GlobalReference + 0173-1#07-ABZ599#003 + + + + + + l + + ExternalReference + + + GlobalReference + 0173-1#07-ABZ600#003 + + + + + + cbm + + ExternalReference + + + GlobalReference + 0173-1#07-ABZ601#003 + + + + + + qm + + ExternalReference + + + GlobalReference + 0173-1#07-ABZ602#003 + + + + + + piece + + ExternalReference + + + GlobalReference + 0173-1#07-ABZ603#003 + + + + + + kWh + + ExternalReference + + + GlobalReference + 0173-1#07-ACB997#001 + + + + + + + + + + + + + PcfApiQuery + + + en + Query + + + de + Abfrage + + + + + de + Spezifische Abfrage, über die Daten zur Berechnung des CO2-Fußabdrucks eines Produkts von externen Quellen abgerufen werden können. + + + en + Specific query that can be used to retrieve data from external sources to calculate the carbon footprint of a product. + + + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/ExternalPcfApi/Query/1/0 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Query + + + de + Abfrage + + + STRING + + + en + Specific query that can be used to retrieve data from external sources to calculate the carbon footprint of a product. + + + de + Spezifische Abfrage, über die Daten zur Berechnung des CO2-Fußabdrucks eines Produkts von externen Quellen abgerufen werden können. + + + + + + + + + ExternalPcfApi + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/ExternalPcfApi/1/0 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + An external service that provisions carbon footprint information via an interface, allowing on-demand retrieval of this data. + + + de + Ein externer Dienst, der über eine Schnittstelle Informationen zum CO2-Fußabdruck bereitstellt und den Abruf dieser Daten auf Abruf ermöglicht. + + + + + en + An external service that provisions carbon footprint information via an interface, allowing on-demand retrieval of this data. + + + de + Ein externer Dienst, der über eine Schnittstelle Informationen zum CO2-Fußabdruck bereitstellt und den Abruf dieser Daten auf Abruf ermöglicht. + + + + + + + + + LifeCyclePhase + 0173-1#02-ABG858#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + life cycle phase (acc. EN 15804) + + + de + Lebenszyklusphase (nach EN 15804) + + + + + en + life cycle phase + + + de + Lebenszyklusphase + + + STRING + + + de + Lebenszyklusphase des Produktes gemäß den Quantifizierungsvorgaben der Norm, auf den sich die PCF-Angabe zum CO2-Fußabdruck bezieht + + + en + life cycle stages of the product according to the quantification requirements of the standard to which the PCF carbon footprint statement refers + + + + + + A1 - raw material supply (and upstream production) + + ExternalReference + + + GlobalReference + 0173-1#07-ABU208#003 + + + + + + A2 - cradle-to-gate transport to factory + + ExternalReference + + + GlobalReference + 0173-1#07-ABU209#003 + + + + + + A3 - production + + ExternalReference + + + GlobalReference + 0173-1#07-ABU210#003 + + + + + + A1-A3 + + ExternalReference + + + GlobalReference + 0173-1#07-ABZ789#003 + + + + + + A4 - transport to final destination + + ExternalReference + + + GlobalReference + 0173-1#07-ABU211#003 + + + + + + A4-A5 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC013#001 + + + + + + A5 - Installation + + ExternalReference + + + GlobalReference + 0173-1#07-ACC016#001 + + + + + + B1 - usage phase + + ExternalReference + + + GlobalReference + 0173-1#07-ABU212#003 + + + + + + B1-B7 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC014#001 + + + + + + B2 - maintenance + + ExternalReference + + + GlobalReference + 0173-1#07-ABV498#003 + + + + + + B3 - repair + + ExternalReference + + + GlobalReference + 0173-1#07-ABV497#003 + + + + + + B4 - Replacement + + ExternalReference + + + GlobalReference + 0173-1#07-ACC017#001 + + + + + + B5 - update/upgrade, refurbishing + + ExternalReference + + + GlobalReference + 0173-1#07-ABV499#003 + + + + + + B6 - usage energy consumption + + ExternalReference + + + GlobalReference + 0173-1#07-ABV500#003 + + + + + + B7 - usage water consumption + + ExternalReference + + + GlobalReference + 0173-1#07-ABV501#003 + + + + + + C1 - reassembly + + ExternalReference + + + GlobalReference + 0173-1#07-ABV502#003 + + + + + + C1-C4 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC015#001 + + + + + + C2 - transport to recycler + + ExternalReference + + + GlobalReference + 0173-1#07-ABU213#003 + + + + + + C2-C4 + + ExternalReference + + + GlobalReference + 0173-1#07-ACC018#001 + + + + + + C3 - recycling, waste treatment + + ExternalReference + + + GlobalReference + 0173-1#07-ABV503#003 + + + + + + C4 - landfill + + ExternalReference + + + GlobalReference + 0173-1#07-ABV504#003 + + + + + + D - reuse + + ExternalReference + + + GlobalReference + 0173-1#07-ABU214#003 + + + + + + + + + + + + + ProductOrSectorSpecificCarbonFootprint + + + en + Product Carbon Footprint, which is determined in accordance with sector or product group-specific rules or guidelines and covers the life cycle or parts of a product life cycle. + + + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificCarbonFootprint/1/0 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Product Or Sector Specific Carbon Footprint + + + + + en + Product Carbon Footprint, which is determined in accordance with sector or product group-specific rules or guidelines and covers the life cycle or parts of a product life cycle. + + + + + + + + + ProductCarbonFootprint + + + de + Produkt CO2-Fußabdruck + + + en + Product carbon footprint + + + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/ProductCarbonFootprint/1/0 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Product Carbon Footprint + + + de + Produkt CO2-Fußabdruck + + + + + en + Balance of greenhouse gas emissions along the entire life cycle of a product in a defined application and in relation to a defined unit of use + + + + + + + + + PcfInformation + + + de + PCF Informationen + + + en + PCF Informationen + + + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/PcfInformation/1/0 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + PCF Information + + + de + PCF Informationen + + + + + en + A section in which further content is listed according to the calculation method for the Product Carbon Footprint. + + + de + Ein Abschnitt, in dem weitere Inhalte entsprechend der Berechnungsmethode zum Product Carbon Footprint aufgeführt werden. + + + + + + + + + PcfCO2eq + 0173-1#02-ABG855#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + CO2 equivalent - total (for PCF and LCA) + + + de + CO2-Äquivalent - total (für PCF und LCA) + + + CO2eq + REAL_MEASURE + + + de + Summe aller Treibhausgas-Emissionen eines Produkts gemäß der Quantifizierungsvorgaben der Norm + + + en + sum of all greenhouse gas emissions of a product according to the quantification requirements of the standard + + + + + + + + + PcfRuleName + + + de + Name der PCF Berechnungsmethode + + + en + Name of the PCF calculation method + + + + 1 + 0 + + https://admin-shell.io/idta/CarbonFootprint/ProductOrSectorSpecificRule/Name/1/0 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Name of the PCF calculation method + + + de + Name der PCF Berechnungsmethode + + + STRING + + + de + Folgenabschätzungsmethode / Berechnungsmethode + + + en + Standard, method for determining the greenhouse gas emissions of a product + + + + + + + + + QuantityOfMeasureForCalculation + + + de + Mengenangabe für die Berechnung + + + en + quantity of measure for calculation + + + 0173-1#02-ABG857#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + quantity of measure for calculation + + + de + Mengenangabe für die Berechnung + + + REAL_COUNT + + + de + Angabe der Stückzahl, Masse oder des Volumens zur Berechnung der Auswirkungen des Klimawandels oder des CO2-Fußabdrucks des Produktes + + + en + provides the quantity number of pieces or mass or volume to compute the impact of climate change or product carbon footprint + + + + + + + + + diff --git a/example/aas/ContactInformationAAS.xml b/example/aas/ContactInformationAAS.xml new file mode 100644 index 0000000..b0f8973 --- /dev/null +++ b/example/aas/ContactInformationAAS.xml @@ -0,0 +1,2139 @@ + + + + ContactInformationAAS + https://admin-shell.io/idta/aas/ContactInformation/1/0 + + Type + https://admin-shell.io/idta/asset/ContactInformation/1/0 + Type + + + + ModelReference + + + Submodel + https://admin-shell.io/idta/SubmodelTemplate/ContactInformation/1/0 + + + + + + + + + ContactInformations + https://admin-shell.io/idta/SubmodelTemplate/ContactInformation/1/0 + Template + + ModelReference + + + Submodel + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations + + + + + + ContactInformation + + + en + The SMC “ContactInformation” contains information on how to contact the manufacturer or an authorised service provider, e.g. when a maintenance service is required + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation + + + + + + ConceptQualifier + Multiplicity + xs:string + OneToMany + + + + + RoleOfContactPerson + + + en + enumeration: 0173-1#07-AAS927#001 (administrativ contact), 0173-1#07-AAS928#001 (commercial contact), 0173-1#07-AAS929#001 (other contact), 0173-1#07-AAS930#001 (hazardous goods contact), 0173-1#07-AAS931#001 (technical contact). Note: the above mentioned ECLASS enumeration should be declared as “open” for further addition. ECLASS enumeration IRDI is preferable. If no IRDI available, custom input as String may also be accepted. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO204#003 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + xs:string + 0173-1#07-AAS931#001 + + + Language + + + en + Note: language codes defined accord. to ISO 639-1. Note: as per ECLASS definition, Expression and representation of thoughts, information, feelings, ideas through characters. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Language + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToMany + + + xs:string + de + + + TimeZone + + + en + Note: notation accord. to ISO 8601 Note: for time in UTC the zone designator “Z” is to be used + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/TimeZone + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + xs:string + Z + + + AddressOfAdditionalLink + + ExternalReference + + + GlobalReference + 0173-1#02-AAQ326#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + xs:string + + + NationalCode + + + en + Note: country codes defined accord. to ISO 3166-1. Recommendation: property declaration as MLP is required by its semantic definition. As the property value is language independent, users are recommended to provide maximal 1 string in any language of the user’s choice. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO134#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + DE + + + + + CityTown + + ExternalReference + + + GlobalReference + 0173-1#02-AAO132#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + de + Musterstadt + + + + + Company + + ExternalReference + + + GlobalReference + 0173-1#02-AAW001#001 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + ABC Company + + + + + Department + + ExternalReference + + + GlobalReference + 0173-1#02-AAO127#003 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + de + Vertrieb + + + + + Street + + ExternalReference + + + GlobalReference + 0173-1#02-AAO128#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + de + Musterstraße 1 + + + + + Zipcode + + + en + Recommendation: property declaration as MLP is required by its semantic definition. As the property value is language independent, users are recommended to provide maximal 1 string in any language of the user’s choice. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO129#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + de + 12345 + + + + + POBox + + ExternalReference + + + GlobalReference + 0173-1#02-AAO130#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + PF 1234 + + + + + ZipCodeOfPOBox + + + en + Recommendation: property declaration as MLP is required by its semantic definition. As the property value is language independent, users are recommended to provide maximal 1 string in any language of the user’s choice. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO131#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + 12345 + + + + + StateCounty + + ExternalReference + + + GlobalReference + 0173-1#02-AAO133#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + de + Muster-Bundesland + + + + + NameOfContact + + ExternalReference + + + GlobalReference + 0173-1#02-AAO205#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + + + + de + + + + + + FirstName + + ExternalReference + + + GlobalReference + 0173-1#02-AAO206#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + + + + de + + + + + + MiddleNames + + ExternalReference + + + GlobalReference + 0173-1#02-AAO207#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + + + + de + + + + + + Title + + ExternalReference + + + GlobalReference + 0173-1#02-AAO208#003 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + a + + + de + a + + + + + AcademicTitle + + ExternalReference + + + GlobalReference + 0173-1#02-AAO209#003 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + + + + de + + + + + + FurtherDetailsOfContact + + ExternalReference + + + GlobalReference + 0173-1#02-AAO210#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + + + + de + + + + + + Phone + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + TelephoneNumber + + + en + Recommendation: property declaration as MLP is required by its semantic definition. As the property value is language independent, users are recommended to provide maximal 1 string in any language of the user’s choice. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO136#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + One + + + + + en + +491234567890 + + + + + AvailableTime + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/AvailableTime/ + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + de + Montag – Freitag 08:00 bis 16:00 + + + + + TypeOfTelephone + + + en + enumeration: 0173-1#07-AAS754#001 (office), 0173-1#07-AAS755#001 (office mobile), 0173-1#07-AAS756#001 (secretary), 0173-1#07-AAS757#001 (substitute), 0173-1#07-AAS758#001 (home), 0173-1#07-AAS759#001 (private mobile) + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO137#003 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + xs:string + 0173-1#07-AAS754#001 + + + + + Fax + + ExternalReference + + + GlobalReference + 0173-1#02-AAQ834#005 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + FaxNumber + + + en + Recommendation: property declaration as MLP is required by its semantic definition. As the property value is language independent, users are recommended to provide maximal 1 string in any language of the user’s choice. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO195#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + One + + + + + en + +491234567890 + + + + + TypeOfFaxNumber + + + en + enumeration: 0173-1#07-AAS754#001 (office), 0173-1#07-AAS756#001 (secretary), 0173-1#07-AAS758#001 (home) + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO196#003 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + xs:string + 0173-1#07-AAS754#001 + + + + + Email + + ExternalReference + + + GlobalReference + 0173-1#02-AAQ836#005 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + EmailAddress + + ExternalReference + + + GlobalReference + 0173-1#02-AAO198#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + One + + + xs:string + email@muster-ag.de + + + TypeOfEmailAddress + + + en + enumeration: 0173-1#07-AAS754#001 (office), 0173-1#07-AAS756#001 (secretary), 0173-1#07-AAS757#001 (substitute), 0173-1#07-AAS758#001 (home) + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO199#003 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + xs:string + 0173-1#07-AAS754#001 + + + PublicKey + + + en + Recommendation: property declaration as MLP is required by its semantic definition. As the property value is language independent, users are recommended to provide maximal 1 string in any language of the user’s choice. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO200#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + o + + + de + a + + + + + TypeOfPublicKey + + ExternalReference + + + GlobalReference + 0173-1#02-AAO201#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + a + + + de + a + + + + + + + IPCommunication__00__ + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/ + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToMany + + + + + AddressOfAdditionalLink + + ExternalReference + + + GlobalReference + 0173-1#02-AAQ326#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + One + + + xs:string + + + TypeOfCommunication + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + xs:string + Chat + + + AvailableTime + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/AvailableTime/ + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + de + Montag – Freitag 08:00 bis 16:00 + + + + + + + + + + + + + NationalCode + 0173-1#02-AAO134#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + NationalCode + + + + + en + code of a country + + + + + + + + + Company + 0173-1#02-AAW001#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Company + + + + + en + name of the company + + + + + + + + + Fax + 0173-1#02-AAQ834#005 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Fax + + + + + en + Fax number including type + + + + + + + + + TimeZone + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/TimeZone + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TimeZone + + + + + en + offsets from Coordinated Universal Time (UTC) + + + + + + + + + FurtherDetailsOfContact + 0173-1#02-AAO210#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + FurtherDetailsOfContact + + + + + en + additional information of the contact person + + + + + + + + + TypeOfEmailAddress + 0173-1#02-AAO199#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TypeOfEmailAddress + + + + + en + characterization of an e-mail address according to its location or usage + + + + + + + + + MiddleNames + 0173-1#02-AAO207#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + MiddleNames + + + + + en + middle names of contact person + + + + + + + + + TypeOfPublicKey + 0173-1#02-AAO201#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TypeOfPublicKey + + + + + en + characterization of a public key according to its encryption process + + + + + + + + + AvailableTime + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/AvailableTime/ + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + AvailableTime + + + + + en + Specification of the available time window + + + + + + + + + Department + 0173-1#02-AAO127#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Department + + + + + en + administrative section within an organisation where a business partner is located + + + + + + + + + ContactInformation + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ContactInformation + + + + + en + The SMC “ContactInformation” contains information on how to contact the manufacturer or an authorised service provider, e.g. when a maintenance service is required + + + + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAQ837#005 + + + + + + + StateCounty + 0173-1#02-AAO133#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + StateCounty + + + + + en + federal state a part of a state + + + + + + + + + PublicKey + 0173-1#02-AAO200#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + PublicKey + + + + + en + public part of an unsymmetrical key pair to sign or encrypt text or messages + + + + + + + + + EmailAddress + 0173-1#02-AAO198#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + EmailAddress + + + + + en + electronic mail address of a business partner + + + + + + + + + Street + 0173-1#02-AAO128#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Street + + + + + en + street name and house number + + + + + + + + + TypeOfTelephone + 0173-1#02-AAO137#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TypeOfTelephone + + + + + en + characterization of a telephone according to its location or usage + + + + + + + + + Title + 0173-1#02-AAO208#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Title + + + + + en + common, formal, religious, or other title preceding a contact person's name + + + + + + + + + RoleOfContactPerson + 0173-1#02-AAO204#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + RoleOfContactPerson + + + + + en + function of a contact person in a process + + + + + + + + + AcademicTitle + 0173-1#02-AAO209#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + AcademicTitle + + + + + en + academic title preceding a contact person's name + + + + + + + + + Language + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Language + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Language + + + + + en + Available language + + + + + + + + + ModelReference + + + Submodel + 0173-1#02-AAO895#003 + + + + + + + CityTown + 0173-1#02-AAO132#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + CityTown + + + + + en + town or city + + + + + + + + + Email + 0173-1#02-AAQ836#005 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Email + + + + + en + E-mail address and encryption method + + + + + + + + + TelephoneNumber + 0173-1#02-AAO136#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TelephoneNumber + + + + + en + complete telephone number to be called to reach a business partner + + + + + + + + + TypeOfFaxNumber + 0173-1#02-AAO196#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TypeOfFaxNumber + + + + + en + characterization of the fax according its location or usage + + + + + + + + + Zipcode + 0173-1#02-AAO129#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Zipcode + + + + + en + ZIP code of address + + + + + + + + + NameOfContact + 0173-1#02-AAO205#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + NameOfContact + + + + + en + surname of a contact person + + + + + + + + + Phone + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Phone + + + + + en + Phone number including type + + + + + + + + + AddressOfAdditionalLink + 0173-1#02-AAQ326#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + AddressOfAdditionalLink + + + + + en + web site address where information about the product or contact is given + + + + + + + + + FaxNumber + 0173-1#02-AAO195#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + FaxNumber + + + + + en + complete telephone number to be called to reach a business partner's fax machine + + + + + + + + + ZipCodeOfPOBox + 0173-1#02-AAO131#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ZipCodeOfPOBox + + + + + en + ZIP code of P.O. box address + + + + + + + + + POBox + 0173-1#02-AAO130#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + POBox + + + + + en + P.O. box number + + + + + + + + + ContactInformations + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ContactInformations + + + + + en + The Submodel “ContactInformations” is the collection for various contact information. + + + + + + + + + FirstName + 0173-1#02-AAO206#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + FirstName + + + + + en + first name of a contact person + + + + + + + + + \ No newline at end of file diff --git a/example/aas/HandoverDocumentation.xml b/example/aas/HandoverDocumentation.xml new file mode 100644 index 0000000..e1fdf49 --- /dev/null +++ b/example/aas/HandoverDocumentation.xml @@ -0,0 +1,3324 @@ + + + + + HandoverDocumentationAAS + https://admin-shell.io/idta/aas/HandoverDocumentation/2/0 + + Type + https://admin-shell.io/idta/asset/HandoverDocumentation/2/0 + Type + + + + ModelReference + + + Submodel + https://admin-shell.io/idta/SubmodelTemplate/HandoverDocumentation/2/0 + + + + + + + + + HandoverDocumentation + + + en + The Submodel defines a set meta data for the handover of documentation from the manufacturer to the operator for industrial equipment + + + + 2 + 0 + https://admin-shell.io/idta-02004-2-0 + + https://admin-shell.io/idta/SubmodelTemplate/HandoverDocumentation/2/0 + Template + + ModelReference + + + Submodel + 0173-1#01-AHF578#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-01-AHF578-003 + + + + + + + Documents + + + en + Documents (handover documentation) + + + de + Dokumente (Übergabedokumentation) + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI500#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI500-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + true + + ExternalReference + + + GlobalReference + 0173-1#02-ABI500#003/0173-1#01-AHF579#003 + + + + SubmodelElementCollection + + + Document + + + en + This SubmodelElementCollection holds the information for a VDI 2770 Document entity + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI500#003/0173-1#01-AHF579#003 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI500#003~0/0173-1#01-AHF579#003 + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI500-003/0173-1-01-AHF579-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + OneToMany + + + + + DocumentIds + + + en + Document identifyers + + + de + Dokumentidentifikatoren + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI501#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI501-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + true + + ExternalReference + + + GlobalReference + 0173-1#02-ABI501#003/0173-1#01-AHF580#003 + + + + SubmodelElementCollection + + + DocumentId + + + en + Document identificator + + + de + Dokumentidentifikator + + + + + en + This SubmodelElementCollection holds the information for a VDI 2770 Document entity + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI501#003/0173-1#01-AHF580#003 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI501#003~0/0173-1#01-AHF580#003 + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI501-003/0173-1-01-AHF580-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + OneToMany + + + + + PARAMETER + DocumentDomainId + + + en + document domain identificator + + + de + Document Domain Identifikator + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABH994#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABH994-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + https://domain.com/... + + + xs:string + + + PARAMETER + DocumentIdentifier + + + en + Document Identifyer + + + de + Dokumentennummer + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO099#004 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-AAO099-004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + XF90-884 + + + xs:string + + + PARAMETER + DocumentIsPrimary + + + en + Document is primary + + + de + Dokument ist primär + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABH995#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABH995-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + true + + + xs:boolean + + + + + + + DocumentClassifications + + + en + Document classifications + + + de + Dokumentklassifikationen + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI502#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI502-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + true + + ExternalReference + + + GlobalReference + 0173-1#02-ABI502#003/0173-1#01-AHF581#003 + + + + SubmodelElementCollection + + + DocumentClassification + + + en + Document classification + + + de + Dokumentklassifikation + + + + + en + Set of information for describing the classification of the Document according to a ClassificationSystem + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI502#003/0173-1#01-AHF581#003 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI502#003~0/0173-1#01-AHF581#003 + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI502-003/0173-1-01-AHF581-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + OneToMany + + + + + PARAMETER + ClassId + + + en + Class identificator + + + de + Klassenidentifikator + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABH996#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABH996-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + 03-02 + + + xs:string + + + PARAMETER + ClassificationSystem + + + en + Classification system + + + de + Klassifizierungssystem + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABH997#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABH997-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + VDI2770:2020 + + + xs:string + + + PARAMETER + ClassName + + + en + Class Name + + + de + Klassenname + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABJ219#002 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABJ219-002 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + Operation@en + + + + + en + + + + de + + + + + + + + + + DocumentVersions + + + en + Document versions + + + de + Dokumentenversionen + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI503#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI503-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + true + + ExternalReference + + + GlobalReference + 0173-1#02-ABI503#003/0173-1#01-AHF582#003 + + + + SubmodelElementCollection + + + DocumentVersion + + + en + Document version + + + de + Document version + + + + + en + Set of information for describing the classification of the Document according to a ClassificationSystem + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI503#003/0173-1#01-AHF582#003 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI503#003~0/0173-1#01-AHF582#003 + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI503-003/0173-1-01-AHF582-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + OneToMany + + + + + Language + + + en + Language + + + de + Sprache + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAN468#008 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-AAN468-008 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + true + Property + xs:string + + + language + + + en + en (English) + + + de + en (Englisch) + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAN468#009 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + OneToMany + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + en + + + xs:string + en + + ExternalReference + + + GlobalReference + 0173-1#07-AAS045#003 + + + + + + + + RefersToEntities + + + en + Reference to other documents + + + de + Referenz zu anderen Dokumenten + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABK288#002 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABK288-002 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + true + ReferenceElement + + + BasedOnReferences + + + en + Based on other documents + + + de + Basiert auf anderen Dokumenten + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABK289#002 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABK289-002 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + true + ReferenceElement + + + TranslationOfEntities + + + en + Translation of other documents + + + de + Übersetzung von anderen Elementen + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABK290#002 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABK290-002 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + true + ReferenceElement + + + DigitalFiles + + + en + Digital files + + + de + Digitale Dateien + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABK126#002 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABK126-002 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + true + File + + + DigitalFile + + + en + Name of the specific digital file@en + + + de + Name der spezifischen digitalen Datei@de + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABK126#003 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + OneToMany + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + docu_cecc_fullmanual_DE.PDF + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0 + + + + ConceptQualifier + AllowedIdShort + xs:string + DigitalFile[\d{2,3}] + + + application/pdf + + + + + PARAMETER + Version + + + en + Document version + + + de + Dokumentenversion + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAP003#005 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-AAP003-005 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + V1.2 + + + xs:string + + + PARAMETER + StatusSetDate + + + en + Document status set date + + + de + Datum der Einstellung des Dokumentenstatus + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI000#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI000-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + 2020-02-06 + + + xs:date + + + PARAMETER + StatusValue + + + en + Document status + + + de + Dokumentstatus + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI001#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI001-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + Released + + + xs:string + + + PARAMETER + OrganizationShortName + + + en + Organization short name + + + de + Kurzname der Organisation + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI002-003 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + Example company + + + xs:string + + + PARAMETER + OrganizationOfficialName + + + en + Organization official name + + + de + Offizieller Name der Organisation + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI004#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABI004-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + Example company Ltd. + + + xs:string + + + PARAMETER + Title + + + en + Document title + + + de + Dokumententitel + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABG940#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABG940-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + Examplary title@en + + + + + en + + + + de + + + + + + PARAMETER + Subtitle + + + en + Subtitle + + + de + Untertitel + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABH998#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABH998-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + Examplary subtitle@en + + + + + en + s + + + de + s + + + + + PARAMETER + Description + + + en + Document description + + + de + Dokumentenbeschreibung + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAN466#004 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-AAN466-004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + Abstract@en + + + + + en + s + + + de + s + + + + + PARAMETER + KeyWords + + + en + Keywords + + + de + Stichworte + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABH999#003 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABH999-003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + Examplary keywords@en + + + + + en + s + + + de + s + + + + + PARAMETER + PreviewFile + + + en + Preview file + + + de + Vorschaudatei + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABK127#002 + + + + + + ExternalReference + + + GlobalReference + https://api.eclass-cdp.com/0173-1-02-ABK127-002 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + docu_cecc_fullmanual_DE.jpg + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0 + + + + ConceptQualifier + AllowedIdShort + xs:string + PreviewFile[\d{2,3}] + + + image/jpeg + + + + + + + DocumentedEntities + + ExternalReference + + + GlobalReference + https://admin-shell.io/vdi/2770/1/0/Document/DocumentedEntities + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + true + ReferenceElement + + + + + + + Entities + + ExternalReference + + + GlobalReference + https://admin-shell.io/vdi/2770/1/0/EntitiesForDocumentation + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + true + Entity + + + + + + + KeyWords + + + en + Keywords + + + de + Stichworte + + + 0173-1#02-ABH999#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Keywords + + + de + Stichworte + + + + + en + Keywords + + + en + Stichworte + + + STRING_TRANSLATABLE + + + en + List of language-dependent keywords of the document + + + de + Liste der sprachabhängigen Schlüsselwörter des Dokuments + + + + + + + + + DocumentDomainId + + + en + document domain identificator + + + de + Document Domain Identifikator + + + 0173-1#02-ABH994#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Document domain id + + + de + Dokument Domain Identifikator + + + + + en + DocDomainId + + + de + DokDomainId + + + STRING + + + en + Identification of the domain in which the given DocumentId is unique. The domain ID can e.g., be the name or acronym of the providing organisation + + + de + Identifikation der Domäne, in der die angegebene DocumentId eindeutig ist. Die Domain-ID kann z. B. der Name oder das Akronym der bereitstellenden Organisation sein + + + + + + + + + DocumentVersion + + + en + Document version + + + de + Document version + + + 0173-1#02-ABI503#003/0173-1#01-AHF582#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Document version + + + en + Document version + + + + + en + DocuVersion + + + en + DokuVersion + + + + + en + Information about a document version entity + + + en + Information für eine Dokumentenversdions-Entität + + + + + + + + + StatusValue + + + en + Document status + + + de + Dokumentstatus + + + 0173-1#02-ABI001#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Document status + + + de + Dokumentstatus + + + + + en + DocStatus + + + de + DokStatus + + + + + en + Each document version represents a point in time in the document life cycle. This status value refers to the milestones in the document life cycle. The following two values should be used for the application of this guideline: InReview (under review), Released (released) + + + de + Jede Dokumentversion repräsentiert einen Zeitpunkt im Dokumentlebenszyklus. Dieser Statuswert bezieht sich auf die Meilensteine ​​im Dokumentenlebenszyklus. Für die Anwendung dieser Richtlinie sollten die folgenden zwei Werte verwendet werden: InReview (in Überprüfung), Released (freigegeben) + + + + + + + + + Description + + + en + Document description + + + de + Dokumentenbeschreibung + + + 0173-1#02-AAN466#004 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Document description + + + de + Dokumentenbeschreibung + + + + + en + DocDescr + + + de + DokBeschreib + + + STRING_TRANSLATABLE + + + en + Plain text characterizing the content of the document + + + de + Klartext, der den Inhalt des Dokuments kennzeichnet + + + + + + + + + ClassificationSystem + + + en + Classification system + + + en + Klassifizierungssystem + + + 0173-1#02-ABH997#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Classification system + + + de + Klassifizierungssystem + + + + + en + ClassSystem + + + de + KlassSystem + + + STRING + + + en + Identification of the classification system + + + en + Identifikation des Klassifikationssystems + + + + + + + + + ClassId + + + en + Class identificator + + + de + Klassenidentifikator + + + 0173-1#02-ABH996#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Class identifyer + + + de + Klassenidentifikator + + + + + en + ClassId + + + de + KlassenId + + + STRING + + + en + Unique ID of the document class within a classficationsystem + + + de + Eindeutige ID der Dokumentenklasse innerhalb eines Klassifikationsystems + + + + + + + + + Title + + + en + Document title + + + de + Dokumententitel + + + 0173-1#02-ABG940#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Document name + + + de + Dokumentenname + + + + + en + DocName + + + de + DokName + + + STRING_TRANSLATABLE + + + en + Name of the document + + + de + Name des Dokuments + + + + + + + + + Document + + + en + Document + + + de + Dokument + + + + + en + This SubmodelElementCollection holds the information for a VDI 2770 Document entity + + + 0173-1#02-ABI500#003/0173-1#01-AHF579#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Document (handover documentation) + + + de + Dokument (Übergabedokumentation) + + + + + en + Document + + + en + Dokument + + + + + en + Each SubmodelElementCollection describes a document by standard, which is associated to the particular Asset Administration Shell + + + de + Jede SubmodelElementCollection beschreibt ein Dokument (siehe IEC 82045-1 und IEC 8245-2), das der jeweiligen Asset Administration Shell zugeordnet ist + + + + + + + + + DocumentIdentifier + + + en + Document identifyer + + + 0173-1#02-AAO099#004 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + DocumentIdentifier + + + be + Dokumentennummer + + + + + en + DocNumber + + + de + DokNummer + + + STRING + + + en + alphanumeric character sequence uniquely identifying a document + + + de + alphanumerische Zeichenfolge, die ein Dokument eindeutig identifiziert + + + + + + + + + HandoverDocumentation + + + en + The Submodel defines a set meta data for the handover of documentation from the manufacturer to the operator for industrial equipment + + + 0173-1#01-AHF578#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + HandoverDocumentation + + + + + en + The Submodel defines a set meta data for the handover of documentation from the manufacturer to the operator for industrial equipment + + + + + + + + + ClassName + + + en + Class Name + + + de + Klassenname + + + 0173-1#02-ABJ219#002 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Class name + + + de + Klassenname + + + + + en + ClassName + + + en + KlassName + + + STRING + + + en + Name of the class in the classification system + + + de + Name der Klasse im Klassifikationssystem + + + + + + + + + OrganizationOfficialName + + + en + Organization official name + + + de + Offizieller Name der Organisation + + + 0173-1#02-ABI004#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Organization official name + + + de + Offizieller Name der Organisation + + + + + en + OfficialName + + + de + OffiziellerName + + + STRING + + + en + Official name of the organization of the author of the document + + + de + Offizieller Name der Organisation des Autors des Dokuments + + + + + + + + + DocumentIsPrimary + + + en + Document is primary + + + de + Dokument ist primär + + + 0173-1#02-ABH995#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + DocumentIsPrimary + + + de + Dokument ist primär + + + + + en + DocPrimary + + + de + DokPrimär + + + BOOLEAN + + + en + Flag indicating that a DocumentId within a collection of at least two DocumentId`s is the ‘primary’ identifier for the document. This is the preferred ID of the document (commonly from the point of view of the owner of the asset) + + + de + Flag, das angibt, dass eine DocumentId innerhalb einer Sammlung von mindestens zwei DocumentIds die „primäre“ Kennung für das Dokument ist. Dies ist die bevorzugte ID des Dokuments (üblicherweise aus Sicht des Eigentümers des Assets) + + + + + + + + + DocumentId + + + en + Document identificator + + + de + Dokumentidentifikator + + + 0173-1#02-ABI501#003/0173-1#01-AHF580#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Document identificator + + + de + Dokumentidentifikator + + + + + en + DocuId + + + de + DokuId + + + + + en + Information about a document identification entity + + + de + Information für eine Dokumentenidentifikations-Entität + + + + + + + + + Subtitle + + + en + Subtitle + + + de + Untertitel + + + 0173-1#02-ABH998#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Subtitle + + + de + Untertitel + + + + + en + Subtitle + + + de + Untertitel + + + STRING_TRANSLATABLE + + + en + List of language-dependent subtitles of the document + + + de + Liste der sprachabhängigen Untertitel des Dokuments + + + + + + + + + StatusSetDate + + + en + Document status set date + + + de + Datum der Einstellung des Dokumentenstatus + + + 0173-1#02-ABI000#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Document status set date + + + de + Datum der Einstellung des Dokumentenstatus + + + + + en + SetDate + + + de + SetDatum + + + + + en + Date when the document status was set + + + de + Datum, an dem der Dokumentenstatus gesetzt wurde + + + + + + + + + Version + + + en + Document version + + + de + Dokumentenversion + + + 0173-1#02-AAP003#005 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Document version + + + de + Dokumentenversion + + + + + en + DocVersion + + + de + DokVersion + + + STRING + + + en + Design that partly deviates from the previous + + + de + Ausführung, die in einigen Punkten von der vorhergehenden abweicht + + + + + + + + + DocumentClassification + + + en + Document classification + + + de + Dokumentklassifikation + + + 0173-1#02-ABI502#003/0173-1#01-AHF581#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Document classification + + + en + Dokumentklassifikation + + + + + en + DocuClass + + + en + DokuKlass + + + + + en + Information about a document classification entity + + + de + Information für eine Dokumentenklassifikations-Entität + + + + + + + + + OrganizationShortName + https://api.eclass-cdp.com/0173-1-02-ABI002-003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + Organization Short Name + + + + + en + Short name of the organization + + + + + + + + + diff --git a/example/aas/Nameplate.xml b/example/aas/Nameplate.xml new file mode 100644 index 0000000..f428fd1 --- /dev/null +++ b/example/aas/Nameplate.xml @@ -0,0 +1,2071 @@ + + + + + DigitalNameplateAAS + https://admin-shell.io/idta/aas/DigitalNameplate/3/0 + + Type + https://admin-shell.io/idta/asset/DigitalNameplate/3/0 + Type + + + + ModelReference + + + Submodel + https://admin-shell.io/idta/SubmodelTemplate/DigitalNameplate/3/0 + + + + + + + + + Nameplate + + + en + Contains the nameplate information attached to the product + + + + 3 + 0 + https://admin-shell.io/idta-02006-3-0 + + https://admin-shell.io/idta/SubmodelTemplate/DigitalNameplate/3/0 + Template + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/nameplate/3/0/Nameplate + + + + + + URIOfTheProduct + + ExternalReference + + + GlobalReference + 0112/2///61987#ABN590#002 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABH173#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + xs:anyURI + https://www.domain-abc.com/Model-Nr-1234/Serial-Nr-5678 + + + ManufacturerProductType + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA300#008 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO057#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + FM-ABC-1234 + + + OrderCodeOfManufacturer + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA950#008 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO227#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + xs:string + FMABC1234 + + + ProductArticleNumberOfManufacturer + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA581#007 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO676#005 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + FM11-ABC22-123456 + + + SerialNumber + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA951#009 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAM556#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + 12345678 + + + YearOfConstruction + + ExternalReference + + + GlobalReference + 0112/2///61987#ABP000#002 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAP906#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + 2022 + + + DateOfManufacture + + ExternalReference + + + GlobalReference + 0112/2///61987#ABB757#007 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAR972#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:date + 2022-01-01 + + + HardwareVersion + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA926#008 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAN270#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + 1.0.0 + + + FirmwareVersion + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA302#006 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAM985#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + 1.0.0 + + + SoftwareVersion + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA601#008 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAM737#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + 1.0.0 + + + CountryOfOrigin + + + en + Note: Country codes defined accord. to DIN EN ISO 3166-1 alpha-2 codes + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABP462#001 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO259#007 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + DE + + + UniqueFacilityIdentifier + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/nameplate/3/0/UniqueFacilityIdentifier + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + 987654321 + + + ManufacturerName + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA565#009 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO677#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + + + de + "Muster AG" + + + + + ManufacturerProductDesignation + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA567#009 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAW338#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + + + en + "ABC-123" + + + + + ManufacturerProductRoot + + ExternalReference + + + GlobalReference + 0112/2///61360_7#AAS011#001 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAU732#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + + en + "flow meter" + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABP464#002 + + + + + + ManufacturerProductFamily + + ExternalReference + + + GlobalReference + 0112/2///61987#ABP464#002 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAU731#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + + en + "Type ABC" + + + + + AddressInformation + + + en + Note: this set of information is defined by SMT drop-in "Address Information" + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/AddressInformation + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/smt-dropin/smt-dropin-use/1/0 + + + + + ExternalReference + + + GlobalReference + 0112/2///61360_7#AAS002#001 + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAQ837#008/0173-1#01-ADR448#008 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + + + AssetSpecificProperties + + ExternalReference + + + GlobalReference + 0173-1#02-ABI218#003/0173-1#01-AGZ672#004 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + + GuidelineSpecificProperties + + ExternalReference + + + GlobalReference + 0173-1#02-ABI219#003/0173-1#01-AHD205#004 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + true + SubmodelElementCollection + + + + ExternalReference + + + GlobalReference + 0173-1#01-AHD205#004 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + OneToMany + + + + + + + + + CompanyLogo + + ExternalReference + + + GlobalReference + 0112/2///61987#ABP463#001 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI776#002 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + image/png + + + Markings + + + en + Note: CE marking is declared as mandatory according to EU Blue Guide + + + + ExternalReference + + + GlobalReference + 0112/2///61360_7#AAS006#001 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI563#003/0173-1#01-AHF849#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + true + SubmodelElementCollection + + + Marking + + + en + Note: CE marking is declared as mandatory according to the Blue Guide of the EU-Commission + + + + ExternalReference + + + GlobalReference + 0112/2///61360_7#AAS009#001 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI564#003/0173-1#01-AHF850#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + OneToMany + + + + + MarkingName + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA231#009 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI190#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + xs:string + 0173-1#07-DAA603#004 + + + DesignationOfCertificateOrApproval + + + en + Note: Approval identifier, reference to the certificate number, to be entered without spaces + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABH783#003 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI975#002 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + KEMA99IECEX1105/128 + + + IssueDate + + + en + Note: format by lexical representation: CCYY-MM-DD Note: to be specified to the day + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABO097#001 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABL774#001 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:date + 2022-01-01 + + + ExpiryDate + + + en + Note: format by lexical representation: CCYY-MM-DD Note: to be specified to the day + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABH830#002 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABL775#001 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:date + 2022-01-01 + + + MarkingAdditionalText + + ExternalReference + + + GlobalReference + 0112/2///61987#ABB146#007 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI192#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:string + 0044 + + + MarkingFile + + ExternalReference + + + GlobalReference + 0112/2///61987#ABO100#002 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI191#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + image/png + + + + + + + + + + + DateOfManufacture + 0112/2///61987#ABB757#007 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + DateOfManufacture + + + DATE + + + en + date when an item was manufactured + + + + + + + + + MarkingAdditionalText__00__ + 0112/2///61987#ABB146#007 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + MarkingAdditionalText__00__ + + + STRING + + + en + where applicable, additional information on the marking in plain text, e.g. the ID-number of the notified body involved in the conformity process + + + + + + + + + MarkingName + 0112/2///61987#ABA231#009 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + MarkingName + + + STRING + + + en + common name of the marking + + + + + + + + + IssueDate + 0112/2///61987#ABO097#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + IssueDate + + + DATE + + + en + date, at which the specified certificate is issued + + + + + + + + + FirmwareVersion + 0112/2///61987#ABA302#006 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + FirmwareVersion + + + STRING + + + en + version of the firmware supplied with the device + + + + + + + + + SerialNumber + 0112/2///61987#ABA951#009 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + SerialNumber + + + STRING + + + en + unique combination of numbers and letters used to identify the device once it has been manufactured + + + + + + + + + ManufacturerName + 0112/2///61987#ABA565#009 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ManufacturerName + + + STRING_TRANSLATABLE + + + en + legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation + + + + + + + + + ManufacturerProductRoot + 0112/2///61360_7#AAS011#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ManufacturerProductRoot + + + STRING_TRANSLATABLE + + + en + top level of a 3 level manufacturer specific product hierarchy + + + + + + + + + YearOfConstruction + 0112/2///61987#ABP000#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + YearOfConstruction + + + STRING + + + en + year in which the manufacturing process is completed + + + + + + + + + UniqueFacilityIdentifier + https://admin-shell.io/idta/nameplate/3/0/UniqueFacilityIdentifier + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + UniqueFacilityIdentifier + + + STRING + + + en + unique string of characters for the identification of locations or buildings involved in a product’s value chain or used by actors involved in a product’s value chain + + + + + + + + + OrderCodeOfManufacturer + 0112/2///61987#ABA950#008 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + OrderCodeOfManufacturer + + + STRING + + + en + unique combination of numbers and letters issued by the manufacturer that is used to identify the device for ordering + + + + + + + + + DesignationOfCertificateOrApproval + 0112/2///61987#ABH783#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + DesignationOfCertificateOrApproval + + + STRING + + + en + alphanumeric character sequence identifying a certificate or approval + + + + + + + + + ManufacturerProductType + 0112/2///61987#ABA300#008 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ManufacturerProductType + + + STRING + + + en + characteristic to differentiate between different products of a product family or special variants + + + + + + + + + ManufacturerProductFamily + 0112/2///61987#ABP464#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ManufacturerProductFamily + + + STRING_TRANSLATABLE + + + en + second level of a 3 level manufacturer specific product hierarchy + + + + + + + + + SoftwareVersion + 0112/2///61987#ABA601#008 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + SoftwareVersion + + + STRING + + + en + version of the software used by the device + + + + + + + + + ProductArticleNumberOfManufacturer + 0112/2///61987#ABA581#007 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ProductArticleNumberOfManufacturer + + + STRING + + + en + unique product identifier of the manufacturer + + + + + + + + + URIOfTheProduct + 0112/2///61987#ABN590#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + URIOfTheProduct + + + + + en + URIOfTheProduct + + + STRING + + + en + unique global identification of the product instance using an universal resource identifier (URI) + + + + + + + + + Marking + + + en + Note: CE marking is declared as mandatory according to the Blue Guide of the EU-Commission + + + 0112/2///61360_7#AAS009#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Marking + + + + + en + Single marking information + + + + + + + + + ExpiryDate + 0112/2///61987#ABH830#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ExpiryDate + + + DATE + + + en + date, at which the specified certificate expires + + + + + + + + + CountryOfOrigin + 0112/2///61987#ABP462#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + CountryOfOrigin + + + STRING + + + en + country where the product was manufactured + + + + + + + + + ManufacturerProductDesignation + 0112/2///61987#ABA567#009 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ManufacturerProductDesignation + + + STRING_TRANSLATABLE + + + en + short description of the product (short text), third or lowest level of a 3 level manufacturer specific product hierarchy + + + + + + + + + HardwareVersion + 0112/2///61987#ABA926#008 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + HardwareVersion + + + STRING + + + en + version of the hardware supplied with the device + + + + + + + + + diff --git a/example/aas/TechnicalData.xml b/example/aas/TechnicalData.xml new file mode 100644 index 0000000..0a3239f --- /dev/null +++ b/example/aas/TechnicalData.xml @@ -0,0 +1,1659 @@ + + + + TechnicalDataAAS + https://admin-shell.io/aas/TechnicalData/1/2 + + ModelReference + + + AssetAdministrationShell + https://admin-shell.io/aas/TechnicalData/1/2 + + + + + Type + https://admin-shell.io/asset/TechnicalData/1/2 + Type + + + + ModelReference + + + Submodel + https://admin-shell.io/ZVEI/TechnicalData/Submodel/1/2 + + + + + + + + + TechnicalData + + + en + Submodel containing techical data of the asset and associated product classificatons. + + + de + Teilmodell, das die technischen Daten der Anlage und die zugehörigen Produktklassifizierungen enthält. + + + + 1 + 2 + + https://admin-shell.io/ZVEI/TechnicalData/Submodel/1/2 + Template + + ModelReference + + + Submodel + https://admin-shell.io/ZVEI/TechnicalData/Submodel/1/2 + + + + + + GeneralInformation + + + en + General information, for example ordering and manufacturer information. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/GeneralInformation/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + One + + + + + PARAMETER + ManufacturerName + + + en + Legally valid designation of the natural or judicial body which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into the market. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO677#002 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + Example Company + + + xs:string + + + PARAMETER + ManufacturerArticleNumber + + + en + unique product identifier of the manufacturer + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO676#003 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + A123-456 + + + xs:string + + + PARAMETER + ManufacturerOrderCode + + + en + By manufactures issued unique combination of numbers and letters used to identify the device for ordering + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO227#002 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + EEA-EX-200-S/47-Q3 + + + xs:string + + + ManufacturerLogo + + + en + Imagefile for logo of manufacturer provided in common format (.png, .jpg). + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/ManufacturerLogo/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + ZeroToOne + + + image/png + + + ProductImage + + + en + Image file for associated product provided in common format (.png, .jpg). + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/ProductImage/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + ZeroToMany + + + image/png + + + PARAMETER + ManufacturerProductDesignation + + + en + Product designation as given by the mnaufacturer. Short description of the product, product group or function (short text) in common language. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAW338#001 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + Electrical energy accelerator@en + + + + + en + a + + + de + a + + + + + + + ProductClassifications + + + en + Product classifications by association of product classes with common classification systems. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/ProductClassifications/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + ZeroToOne + + + + + ProductClassificationItem + + + en + Single product classification item by association with product class in a particular classification system or property dictionary. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/ProductClassificationItem/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + ZeroToMany + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0 + + + + ConceptQualifier + AllowedIdShort + xs:string + ProductClassificationItem[\d{2,3}] + + + + + PARAMETER + ProductClassificationSystem + + + en + Common name of the classification system. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/ProductClassificationSystem/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + ECLASS + + + xs:string + + + PARAMETER + ClassificationSystemVersion + + + en + Common version identifier of the used classification system, in order to distinguish different version of the property dictionary. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/ClassificationSystemVersion/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + ZeroToOne + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + 9.0 (BASIC) + + + xs:string + + + PARAMETER + ProductClassId + + + en + Class of the associated product or industrial equipment in the classification system. According to the notation of the system. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/ProductClassId/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + 27-01-88-77 | 0112/2///61987#ABA827#003 + + + xs:string + + + + + + + TechnicalProperties + + + en + Individual characteristics that describe the product and its technical properties. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/TechnicalProperties/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + One + + + + + MainSection + + + en + Main subdivision possibility for properties. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/MainSection/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + ZeroToMany + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0 + + + + ConceptQualifier + AllowedIdShort + xs:string + MainSection[\d{2,3}] + + + + + SubSection + + + en + Subordinate subdivision possibility for properties. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/SubSection/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + ZeroToMany + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0 + + + + ConceptQualifier + AllowedIdShort + xs:string + SubSection[\d{2,3}] + + + + + + + SubSection + + + en + Subordinate subdivision possibility for properties. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/SubSection/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + ZeroToMany + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0 + + + + ConceptQualifier + AllowedIdShort + xs:string + SubSection[\d{2,3}] + + + + + + + FurtherInformation + + + en + Further information on the product, the validity of the information provided and this data record. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/FurtherInformation/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + ZeroToOne + + + + + PARAMETER + TextStatement + + + en + Statement by the manufacturer in text form, e.g. scope of validity of the statements, scopes of application, conditions of operation. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/TextStatement/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + ZeroToMany + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + Restricted use@en + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0 + + + + ConceptQualifier + AllowedIdShort + xs:string + TextStatement[\d{2,3}] + + + + + en + a + + + de + a + + + + + PARAMETER + ValidDate + + + en + Denotes a date on which the data specified in the Submodel was valid from for the associated asset. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/ZVEI/TechnicalData/ManufacturerOrderCode/1/1 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + ConceptQualifier + Cardinality + xs:string + One + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0 + + + + ConceptQualifier + ExampleValue + xs:string + 5/28/2021 + + + xs:date + + + + + + + + + TechnicalData + + + en + Submodel containing techical data of the asset and associated product classificatons. + + + de + Teilmodell, das die technischen Daten der Anlage und die zugehörigen Produktklassifizierungen enthält. + + + https://admin-shell.io/ZVEI/TechnicalData/Submodel/1/2 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + TechnicalData + + + + + en + Submodel containing techical data of the asset and associated product classificatons. + + + + + + + + + GeneralInformation + + + en + General information, for example ordering and manufacturer information. + + + https://admin-shell.io/ZVEI/TechnicalData/GeneralInformation/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + GeneralInformation + + + + + en + General information, for example ordering and manufacturer information. + + + + + + + + + ProductClassificationSystem + + + en + Common name of the classification system. + + + https://admin-shell.io/ZVEI/TechnicalData/ProductClassificationSystem/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + ProductClassificationSystem + + + + + en + Common name of the classification system. + + + + + + + + + ManufacturerName + + + en + Legally valid designation of the natural or judicial body which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into the market. + + + 0173-1#02-AAO677#002 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + ManufacturerName + + + + + en + Legally valid designation of the natural or judicial body which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into the market. + + + + + + + + + TextStatement + + + en + Statement by the manufacturer in text form, e.g. scope of validity of the statements, scopes of application, conditions of operation. + + + https://admin-shell.io/ZVEI/TechnicalData/TextStatement/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + TextStatement + + + + + en + Statement by the manufacturer in text form, e.g. scope of validity of the statements, scopes of application, conditions of operation. + + + + + + + + + FurtherInformation + + + en + Further information on the product, the validity of the information provided and this data record. + + + https://admin-shell.io/ZVEI/TechnicalData/FurtherInformation/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + FurtherInformation + + + + + en + Further information on the product, the validity of the information provided and this data record. + + + + + + + + + ProductClassifications + + + en + Product classifications by association of product classes with common classification systems. + + + https://admin-shell.io/ZVEI/TechnicalData/ProductClassifications/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + ProductClassifications + + + + + en + Product classifications by association of product classes with common classification systems. + + + + + + + + + SubSection + + + en + Subordinate subdivision possibility for properties. + + + https://admin-shell.io/ZVEI/TechnicalData/SubSection/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + SubSection + + + + + en + Subordinate subdivision possibility for properties. + + + + + + + + + ManufacturerOrderCode + + + en + By manufactures issued unique combination of numbers and letters used to identify the device for ordering + + + 0173-1#02-AAO227#002 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + ManufacturerOrderCode + + + + + en + By manufactures issued unique combination of numbers and letters used to identify the device for ordering + + + + + + + + + ProductClassificationItem + + + en + Single product classification item by association with product class in a particular classification system or property dictionary. + + + https://admin-shell.io/ZVEI/TechnicalData/ProductClassificationItem/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + ProductClassificationItem + + + + + en + Single product classification item by association with product class in a particular classification system or property dictionary. + + + + + + + + + ProductClassId + + + en + Class of the associated product or industrial equipment in the classification system. According to the notation of the system. + + + https://admin-shell.io/ZVEI/TechnicalData/ProductClassId/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + ProductClassId + + + + + en + Class of the associated product or industrial equipment in the classification system. According to the notation of the system. + + + + + + + + + ManufacturerArticleNumber + + + en + unique product identifier of the manufacturer + + + 0173-1#02-AAO676#003 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + ManufacturerArticleNumber + + + + + en + unique product identifier of the manufacturer + + + + + + + + + ValidDate + + + en + Denotes a date on which the data specified in the Submodel was valid from for the associated asset. + + + https://admin-shell.io/ZVEI/TechnicalData/ManufacturerOrderCode/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + ValidDate + + + + + en + Denotes a date on which the data specified in the Submodel was valid from for the associated asset. + + + + + + + + + ClassificationSystemVersion + + + en + Common version identifier of the used classification system, in order to distinguish different version of the property dictionary. + + + https://admin-shell.io/ZVEI/TechnicalData/ClassificationSystemVersion/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + ClassificationSystemVersion + + + + + en + Common version identifier of the used classification system, in order to distinguish different version of the property dictionary. + + + + + + + + + MainSection + + + en + Main subdivision possibility for properties. + + + https://admin-shell.io/ZVEI/TechnicalData/MainSection/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + MainSection + + + + + en + Main subdivision possibility for properties. + + + + + + + + + TechnicalProperties + + + en + Individual characteristics that describe the product and its technical properties. + + + https://admin-shell.io/ZVEI/TechnicalData/TechnicalProperties/1/1 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + TechnicalProperties + + + + + en + Individual characteristics that describe the product and its technical properties. + + + + + + + + + ManufacturerProductDesignation + + + en + Product designation as given by the mnaufacturer. Short description of the product, product group or function (short text) in common language. + + + 0173-1#02-AAW338#001 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + ManufacturerProductDesignation + + + + + en + Product designation as given by the mnaufacturer. Short description of the product, product group or function (short text) in common language. + + + + + + + + + \ No newline at end of file diff --git a/apiCollection/Aas Registry/Get All ShellDescriptors.bru b/example/apiCollection/Aas Registry/Get All ShellDescriptors.bru similarity index 100% rename from apiCollection/Aas Registry/Get All ShellDescriptors.bru rename to example/apiCollection/Aas Registry/Get All ShellDescriptors.bru diff --git a/example/apiCollection/Aas Registry/Get Shell Descriptor By Id - Product1.bru b/example/apiCollection/Aas Registry/Get Shell Descriptor By Id - Product1.bru new file mode 100644 index 0000000..549bcf8 --- /dev/null +++ b/example/apiCollection/Aas Registry/Get Shell Descriptor By Id - Product1.bru @@ -0,0 +1,25 @@ +meta { + name: Get Shell Descriptor By Id - Product1 + type: http + seq: 2 +} + +get { + url: {{DataEngineBaseUrl}}/shell-descriptors/:aasIdentifier + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-1}} +} + +script:pre-request { + + +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Registry/Get Shell Descriptor By Id - Product2.bru b/example/apiCollection/Aas Registry/Get Shell Descriptor By Id - Product2.bru new file mode 100644 index 0000000..f4ece14 --- /dev/null +++ b/example/apiCollection/Aas Registry/Get Shell Descriptor By Id - Product2.bru @@ -0,0 +1,25 @@ +meta { + name: Get Shell Descriptor By Id - Product2 + type: http + seq: 3 +} + +get { + url: {{DataEngineBaseUrl}}/shell-descriptors/:aasIdentifier + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-2}} +} + +script:pre-request { + + +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Registry/Get Shell Descriptor By Id - Product3.bru b/example/apiCollection/Aas Registry/Get Shell Descriptor By Id - Product3.bru new file mode 100644 index 0000000..69a7745 --- /dev/null +++ b/example/apiCollection/Aas Registry/Get Shell Descriptor By Id - Product3.bru @@ -0,0 +1,25 @@ +meta { + name: Get Shell Descriptor By Id - Product3 + type: http + seq: 4 +} + +get { + url: {{DataEngineBaseUrl}}/shell-descriptors/:aasIdentifier + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-3}} +} + +script:pre-request { + + +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Registry/folder.bru b/example/apiCollection/Aas Registry/folder.bru new file mode 100644 index 0000000..4d45dcd --- /dev/null +++ b/example/apiCollection/Aas Registry/folder.bru @@ -0,0 +1,8 @@ +meta { + name: Aas Registry + seq: 2 +} + +auth { + mode: inherit +} diff --git a/example/apiCollection/Aas Repository/Product1/Get Asset Information By Id.bru b/example/apiCollection/Aas Repository/Product1/Get Asset Information By Id.bru new file mode 100644 index 0000000..eb0df9f --- /dev/null +++ b/example/apiCollection/Aas Repository/Product1/Get Asset Information By Id.bru @@ -0,0 +1,20 @@ +meta { + name: Get Asset Information By Id + type: http + seq: 1 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/asset-information + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product1/Get Shell By Id.bru b/example/apiCollection/Aas Repository/Product1/Get Shell By Id.bru new file mode 100644 index 0000000..2f49754 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product1/Get Shell By Id.bru @@ -0,0 +1,20 @@ +meta { + name: Get Shell By Id + type: http + seq: 1 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product1/Get Submodel Ref By Id.bru b/example/apiCollection/Aas Repository/Product1/Get Submodel Ref By Id.bru new file mode 100644 index 0000000..fad3978 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product1/Get Submodel Ref By Id.bru @@ -0,0 +1,25 @@ +meta { + name: Get Submodel Ref By Id + type: http + seq: 2 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodel-refs?limit&cursor + body: none + auth: inherit +} + +params:query { + limit: + cursor: +} + +params:path { + aasIdentifier: {{aasIdentifier-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product1/folder.bru b/example/apiCollection/Aas Repository/Product1/folder.bru new file mode 100644 index 0000000..a64c644 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product1/folder.bru @@ -0,0 +1,8 @@ +meta { + name: Product1 + seq: 1 +} + +auth { + mode: inherit +} diff --git a/example/apiCollection/Aas Repository/Product2/Get Asset Information By Id.bru b/example/apiCollection/Aas Repository/Product2/Get Asset Information By Id.bru new file mode 100644 index 0000000..72d5c9b --- /dev/null +++ b/example/apiCollection/Aas Repository/Product2/Get Asset Information By Id.bru @@ -0,0 +1,20 @@ +meta { + name: Get Asset Information By Id + type: http + seq: 1 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/asset-information + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-2}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product2/Get Shell By Id.bru b/example/apiCollection/Aas Repository/Product2/Get Shell By Id.bru new file mode 100644 index 0000000..e0901d9 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product2/Get Shell By Id.bru @@ -0,0 +1,20 @@ +meta { + name: Get Shell By Id + type: http + seq: 2 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-2}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product2/Get Submodel Ref By Id.bru b/example/apiCollection/Aas Repository/Product2/Get Submodel Ref By Id.bru new file mode 100644 index 0000000..43cd8ea --- /dev/null +++ b/example/apiCollection/Aas Repository/Product2/Get Submodel Ref By Id.bru @@ -0,0 +1,25 @@ +meta { + name: Get Submodel Ref By Id + type: http + seq: 3 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodel-refs?limit&cursor + body: none + auth: inherit +} + +params:query { + limit: + cursor: +} + +params:path { + aasIdentifier: {{aasIdentifier-2}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product2/folder.bru b/example/apiCollection/Aas Repository/Product2/folder.bru new file mode 100644 index 0000000..eef6323 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product2/folder.bru @@ -0,0 +1,8 @@ +meta { + name: Product2 + seq: 2 +} + +auth { + mode: inherit +} diff --git a/example/apiCollection/Aas Repository/Product3/Get Asset Information By Id.bru b/example/apiCollection/Aas Repository/Product3/Get Asset Information By Id.bru new file mode 100644 index 0000000..62e52a2 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product3/Get Asset Information By Id.bru @@ -0,0 +1,20 @@ +meta { + name: Get Asset Information By Id + type: http + seq: 1 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/asset-information + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-3}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product3/Get Shell By Id.bru b/example/apiCollection/Aas Repository/Product3/Get Shell By Id.bru new file mode 100644 index 0000000..1fe6f17 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product3/Get Shell By Id.bru @@ -0,0 +1,20 @@ +meta { + name: Get Shell By Id + type: http + seq: 2 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-3}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product3/Get Submodel Ref By Id.bru b/example/apiCollection/Aas Repository/Product3/Get Submodel Ref By Id.bru new file mode 100644 index 0000000..cf736ec --- /dev/null +++ b/example/apiCollection/Aas Repository/Product3/Get Submodel Ref By Id.bru @@ -0,0 +1,25 @@ +meta { + name: Get Submodel Ref By Id + type: http + seq: 3 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodel-refs?limit&cursor + body: none + auth: inherit +} + +params:query { + limit: + cursor: +} + +params:path { + aasIdentifier: {{aasIdentifier-3}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product3/folder.bru b/example/apiCollection/Aas Repository/Product3/folder.bru new file mode 100644 index 0000000..a102df2 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product3/folder.bru @@ -0,0 +1,8 @@ +meta { + name: Product3 + seq: 3 +} + +auth { + mode: inherit +} diff --git a/example/apiCollection/Aas Repository/folder.bru b/example/apiCollection/Aas Repository/folder.bru new file mode 100644 index 0000000..3720e72 --- /dev/null +++ b/example/apiCollection/Aas Repository/folder.bru @@ -0,0 +1,8 @@ +meta { + name: Aas Repository + seq: 3 +} + +auth { + mode: inherit +} diff --git a/example/apiCollection/README.md b/example/apiCollection/README.md new file mode 100644 index 0000000..d364248 --- /dev/null +++ b/example/apiCollection/README.md @@ -0,0 +1,160 @@ +# Bruno API Testing Setup – DataEngine (.NET Backend) + +## Overview + +This directory contains the Bruno collection and instructions to test the **AAS.TwinEngine.DataEngine** .NET API using Bruno. The collection includes pre-configured requests and environments to exercise the DataEngine API and its plugin-based data sources. + +--- + +## Quick Summary + +| Item | Description | +|--------------------------|-----------------------------------------------------| +| **API** | `AAS.TwinEngine.DataEngine` (.NET) | +| **Testing Tool** | [Bruno](https://www.usebruno.com/downloads) | +| **Default API URL** | `http://localhost:8080` | +| **SDK Required** | .NET 8 (recommended) | +| **Run docker compose file** | Run `docker-compose-up` [form AasTwin.DataEngine](../README.md) | + +--- + +## Prerequisites + +1. **Install Bruno** + + * Download: [https://www.usebruno.com/downloads](https://www.usebruno.com/downloads) + * Platforms: Windows, macOS, Linux + +2. **Install .NET SDK** + + * Recommended: **.NET 8** (install from Microsoft docs) + +3. **Install docker** + +--- + +## Running the services + + +### 1. Run docker compose file + +Before starting , run twinengine environmnet with dpp-plugin. +[click here for getting starated with docker-compose](../README.md) + + +## Bruno Collection — Quick Start + +1. Open Bruno +2. `Collection -> Open Collection` and choose the Bruno collection folder (`apiCollection`) from the AasTwin.DataEngine repository +3. From the top-right environment dropdown select an environment: `local` +4. Expand folders to find requests, select a request and click **Send** +5. Inspect the request/response in the right panel + +--- + +## Bruno environment & collection variables + +The collection includes a set of environment/collection variables you can edit to point the requests at your local or dev instance. +**Enter these variables in plain text — the collection’s Pre-request script will automatically change value to Base64-encode.** + +| Variable name | Purpose | Example value | +| ------------------------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| `DataEngineBaseUrl` | Base URL for DataEngine API | `http://localhost:8080` | +| `productId-1` | Product ID for first asset | `000-001` | +| `productId-2` | Product ID for second asset | `000-002` | +| `productId-3` | Product ID for third asset | `001-001` | +| `aasIdentifier-1` | AAS identifier (auto-encoded to Base64 by script) | `https://mm-software.com/ids/aas/000-001` | +| `aasIdentifier-2` | AAS identifier (auto-encoded to Base64 by script) | `https://mm-software.com/ids/aas/000-002` | +| `aasIdentifier-3` | AAS identifier (auto-encoded to Base64 by script) | `https://mm-software.com/ids/aas/001-001` | +| `submodelIdentifierContact-1` | Submodel identifier for ContactInformation (auto-encoded) | `https://mm-software.com/submodel/000-001/ContactInformation` | +| `submodelIdentifierNameplate-1` | Submodel identifier for Nameplate (auto-encoded) | `https://mm-software.com/submodel/000-001/Nameplate` | +| `submodelIdentifierTechnicalData-1` | Submodel identifier for TechnicalData (auto-encoded) | `https://mm-software.com/submodel/000-001/TechnicalData` | +| `submodelIdentifierCarbonFootprint-1` | Submodel identifier for CarbonFootprint (auto-encoded) | `https://mm-software.com/submodel/000-001/CarbonFootprint` | +| `submodelIdentifierHandoverDocumentation-1` | Submodel identifier for HandoverDocumentation (auto-encoded) | `https://mm-software.com/submodel/000-001/HandoverDocumentation` | + +**Note:** All identifier variables (aasIdentifier-*, submodelIdentifier-*) are automatically Base64-encoded by the collection's pre-request script. Enter plain URLs as shown above. + +--- + +## Default api-test configuration + +* The default configuration includes four shell descriptors with these IDs: + + * `https://mm-software.com/ids/aas/000-001` + * `https://mm-software.com/ids/aas/000-002` + * `https://mm-software.com/ids/aas/001-001` + +* Default submodel templates (under `../aas`): + + * `ContactInformation` + * `Nameplate` + * `HandoverDocumentation` + * `CarbonFootprint` + * `TechnicalData` + +* Default shell template used by all 5 shells: + +```json +{ + "id": "https://mm-software.com/aas/aasTemplate", + "assetInformation": { + "assetKind": "Instance" + }, + "submodels": [ + { + "type": "ModelReference", + "keys": [ + { "type": "Submodel", "value": "Nameplate" } + ] + }, + { + "type": "ModelReference", + "keys": [ + { "type": "Submodel", "value": "ContactInformation" } + ] + }, + { + "type": "ModelReference", + "keys": [ + { "type": "Submodel", "value": "HandoverDocumentation" } + ] + }, + { + "type": "ModelReference", + "keys": [ + { "type": "Submodel", "value": "CarbonFootprint" } + ] + }, + { + "type": "ModelReference", + "keys": [ + { "type": "Submodel", "value": "TechnicalData" } + ] + } + ], + "modelType": "AssetAdministrationShell" +} +``` + +--- + +## Useful requests & folders + +* **Aas Registry** — endpoints to get all ShellDescriptors and ShellDescriptor by id +* **Aas Repository** — endpoints to get Shell by id, SubmodelRef by id, Asset Information by id +* **Submodel Registry** — endpoints to get SubmodelDescriptor by id +* **Submodel Repository** — endpoints to get submodel, submodelElement, and serialization + +(Each Bruno request contains example payloads.) + +--- + +## Troubleshooting + +#### Bruno shows `SSL/TLS handshake failed` + +- Run `dotnet dev-certs https --trust` +- Ensure plugin and API endpoints match port and schema (`https://`) + + +--- diff --git a/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - CarbonFootprint.bru b/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - CarbonFootprint.bru new file mode 100644 index 0000000..77916ba --- /dev/null +++ b/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - CarbonFootprint.bru @@ -0,0 +1,20 @@ +meta { + name: Get Submodel Descriptor By Id - CarbonFootprint + type: http + seq: 3 +} + +get { + url: {{DataEngineBaseUrl}}/submodel-descriptors/:submodelIdentifier + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierCarbonFootprint-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - Contact.bru b/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - Contact.bru new file mode 100644 index 0000000..a9265fc --- /dev/null +++ b/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - Contact.bru @@ -0,0 +1,20 @@ +meta { + name: Get Submodel Descriptor By Id - Contact + type: http + seq: 1 +} + +get { + url: {{DataEngineBaseUrl}}/submodel-descriptors/:submodelIdentifier + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierContact-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - HandoverDocumentation.bru b/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - HandoverDocumentation.bru new file mode 100644 index 0000000..66fb37a --- /dev/null +++ b/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - HandoverDocumentation.bru @@ -0,0 +1,20 @@ +meta { + name: Get Submodel Descriptor By Id - HandoverDocumentation + type: http + seq: 4 +} + +get { + url: {{DataEngineBaseUrl}}/submodel-descriptors/:submodelIdentifier + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierHandoverDocumentation-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - Nameplate.bru b/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - Nameplate.bru new file mode 100644 index 0000000..08b73d4 --- /dev/null +++ b/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - Nameplate.bru @@ -0,0 +1,20 @@ +meta { + name: Get Submodel Descriptor By Id - Nameplate + type: http + seq: 2 +} + +get { + url: {{DataEngineBaseUrl}}/submodel-descriptors/:submodelIdentifier + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierNameplate-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - TechnicalData.bru b/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - TechnicalData.bru new file mode 100644 index 0000000..e2b1dfc --- /dev/null +++ b/example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id - TechnicalData.bru @@ -0,0 +1,20 @@ +meta { + name: Get Submodel Descriptor By Id - TechnicalData + type: http + seq: 5 +} + +get { + url: {{DataEngineBaseUrl}}/submodel-descriptors/:submodelIdentifier + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierTechnicalData-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Registry/folder.bru b/example/apiCollection/Submodel Registry/folder.bru new file mode 100644 index 0000000..bd3aeb4 --- /dev/null +++ b/example/apiCollection/Submodel Registry/folder.bru @@ -0,0 +1,8 @@ +meta { + name: Submodel Registry + seq: 4 +} + +auth { + mode: inherit +} diff --git a/example/apiCollection/Submodel Repository/Serialization/Get appropriate serialization - Product1.bru b/example/apiCollection/Submodel Repository/Serialization/Get appropriate serialization - Product1.bru new file mode 100644 index 0000000..da9bd6d --- /dev/null +++ b/example/apiCollection/Submodel Repository/Serialization/Get appropriate serialization - Product1.bru @@ -0,0 +1,26 @@ +meta { + name: Get appropriate serialization - Product1 + type: http + seq: 1 +} + +get { + url: {{DataEngineBaseUrl}}/serialization?aasIds={{aasIdentifier-1}}&submodelIds={{submodelIdentifierContact-1}}&submodelIds={{submodelIdentifierNameplate-1}}&submodelIds={{submodelIdentifierCarbonFootprint-1}}&submodelIds={{submodelIdentifierHandoverDocumentation-1}}&submodelIds={{submodelIdentifierTechnicalData-1}}&includeConceptDescriptions=false + body: none + auth: inherit +} + +params:query { + aasIds: {{aasIdentifier-1}} + submodelIds: {{submodelIdentifierContact-1}} + submodelIds: {{submodelIdentifierNameplate-1}} + submodelIds: {{submodelIdentifierCarbonFootprint-1}} + submodelIds: {{submodelIdentifierHandoverDocumentation-1}} + submodelIds: {{submodelIdentifierTechnicalData-1}} + includeConceptDescriptions: false +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/apiCollection/Submodel Repository/Serialization/folder.bru b/example/apiCollection/Submodel Repository/Serialization/folder.bru similarity index 100% rename from apiCollection/Submodel Repository/Serialization/folder.bru rename to example/apiCollection/Submodel Repository/Serialization/folder.bru diff --git a/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - CarbonFootprint.bru b/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - CarbonFootprint.bru new file mode 100644 index 0000000..9ee3129 --- /dev/null +++ b/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - CarbonFootprint.bru @@ -0,0 +1,21 @@ +meta { + name: Get Submodel Element - CarbonFootprint + type: http + seq: 3 +} + +get { + url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier/submodel-elements/:idShortPath + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierCarbonFootprint-1}} + idShortPath: ProductCarbonFootprints[0].LifeCyclePhases[0] +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - ContactInfo.bru b/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - ContactInfo.bru new file mode 100644 index 0000000..9471771 --- /dev/null +++ b/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - ContactInfo.bru @@ -0,0 +1,21 @@ +meta { + name: Get Submodel Element - ContactInfo + type: http + seq: 1 +} + +get { + url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier/submodel-elements/:idShortPath + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierContact-1}} + idShortPath: ContactInformation1 +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - HandoverDocumentation.bru b/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - HandoverDocumentation.bru new file mode 100644 index 0000000..be59fe6 --- /dev/null +++ b/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - HandoverDocumentation.bru @@ -0,0 +1,21 @@ +meta { + name: Get Submodel Element - HandoverDocumentation + type: http + seq: 5 +} + +get { + url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier/submodel-elements/:idShortPath + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierHandoverDocumentation-1}} + idShortPath: Documents[0].DocumentClassifications[1].ClassName +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Nameplate.bru b/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Nameplate.bru new file mode 100644 index 0000000..5203039 --- /dev/null +++ b/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Nameplate.bru @@ -0,0 +1,21 @@ +meta { + name: Get Submodel Element - Nameplate + type: http + seq: 2 +} + +get { + url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier/submodel-elements/:idShortPath + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierNameplate-1}} + idShortPath: ManufacturerName +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - TechnicalData.bru b/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - TechnicalData.bru new file mode 100644 index 0000000..34ec067 --- /dev/null +++ b/example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - TechnicalData.bru @@ -0,0 +1,21 @@ +meta { + name: Get Submodel Element - TechnicalData + type: http + seq: 4 +} + +get { + url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier/submodel-elements/:idShortPath + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierTechnicalData-1}} + idShortPath: GeneralInformation.ProductImage +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Repository/Submodel Element/folder.bru b/example/apiCollection/Submodel Repository/Submodel Element/folder.bru new file mode 100644 index 0000000..04497c4 --- /dev/null +++ b/example/apiCollection/Submodel Repository/Submodel Element/folder.bru @@ -0,0 +1,8 @@ +meta { + name: Submodel Element + seq: 2 +} + +auth { + mode: inherit +} diff --git a/example/apiCollection/Submodel Repository/Submodel/Get Submodel - ContactInfo.bru b/example/apiCollection/Submodel Repository/Submodel/Get Submodel - ContactInfo.bru new file mode 100644 index 0000000..e927e4b --- /dev/null +++ b/example/apiCollection/Submodel Repository/Submodel/Get Submodel - ContactInfo.bru @@ -0,0 +1,20 @@ +meta { + name: Get Submodel - ContactInfo + type: http + seq: 1 +} + +get { + url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierContact-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Repository/Submodel/Get Submodel - HandoverDocumentation.bru b/example/apiCollection/Submodel Repository/Submodel/Get Submodel - HandoverDocumentation.bru new file mode 100644 index 0000000..07c7652 --- /dev/null +++ b/example/apiCollection/Submodel Repository/Submodel/Get Submodel - HandoverDocumentation.bru @@ -0,0 +1,20 @@ +meta { + name: Get Submodel - HandoverDocumentation + type: http + seq: 5 +} + +get { + url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierHandoverDocumentation-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Repository/Submodel/Get Submodel - Nameplate.bru b/example/apiCollection/Submodel Repository/Submodel/Get Submodel - Nameplate.bru new file mode 100644 index 0000000..f69644b --- /dev/null +++ b/example/apiCollection/Submodel Repository/Submodel/Get Submodel - Nameplate.bru @@ -0,0 +1,20 @@ +meta { + name: Get Submodel - Nameplate + type: http + seq: 2 +} + +get { + url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier/ + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierNameplate-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Repository/Submodel/Get Submodel - TechnicalData.bru b/example/apiCollection/Submodel Repository/Submodel/Get Submodel - TechnicalData.bru new file mode 100644 index 0000000..2a645ba --- /dev/null +++ b/example/apiCollection/Submodel Repository/Submodel/Get Submodel - TechnicalData.bru @@ -0,0 +1,20 @@ +meta { + name: Get Submodel - TechnicalData + type: http + seq: 3 +} + +get { + url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierTechnicalData-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Submodel Repository/Submodel/Get Submodel -CarbonFootprint.bru b/example/apiCollection/Submodel Repository/Submodel/Get Submodel -CarbonFootprint.bru new file mode 100644 index 0000000..847d0e2 --- /dev/null +++ b/example/apiCollection/Submodel Repository/Submodel/Get Submodel -CarbonFootprint.bru @@ -0,0 +1,15 @@ +meta { + name: Get Submodel - CarbonFootprint + type: http + seq: 4 +} + +get { + url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierCarbonFootprint-1}} +} diff --git a/apiCollection/Submodel Repository/Submodel/folder.bru b/example/apiCollection/Submodel Repository/Submodel/folder.bru similarity index 100% rename from apiCollection/Submodel Repository/Submodel/folder.bru rename to example/apiCollection/Submodel Repository/Submodel/folder.bru diff --git a/example/apiCollection/Submodel Repository/folder.bru b/example/apiCollection/Submodel Repository/folder.bru new file mode 100644 index 0000000..a5f1714 --- /dev/null +++ b/example/apiCollection/Submodel Repository/folder.bru @@ -0,0 +1,8 @@ +meta { + name: Submodel Repository + seq: 5 +} + +auth { + mode: inherit +} diff --git a/example/apiCollection/bruno.json b/example/apiCollection/bruno.json new file mode 100644 index 0000000..1e3ad27 --- /dev/null +++ b/example/apiCollection/bruno.json @@ -0,0 +1,9 @@ +{ + "version": "1", + "name": "DataEngine-DPPPlugin", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ] +} \ No newline at end of file diff --git a/example/apiCollection/collection.bru b/example/apiCollection/collection.bru new file mode 100644 index 0000000..00ee856 --- /dev/null +++ b/example/apiCollection/collection.bru @@ -0,0 +1,38 @@ +vars:pre-request { + aasIdentifier-1: https://mm-software.com/ids/aas/{{productId-1}} + submodelIdentifierContact-1: https://mm-software.com/submodel/{{productId-1}}/ContactInformation + submodelIdentifierNameplate-1: https://mm-software.com/submodel/{{productId-1}}/Nameplate + aasIdentifier-2: https://mm-software.com/ids/aas/{{productId-2}} + aasIdentifier-3: https://mm-software.com/ids/aas/{{productId-3}} + submodelIdentifierTechnicalData-1: https://mm-software.com/submodel/{{productId-1}}/TechnicalData + submodelIdentifierCarbonFootprint-1: https://mm-software.com/submodel/{{productId-1}}/CarbonFootprint + submodelIdentifierHandoverDocumentation-1: https://mm-software.com/submodel/{{productId-1}}/HandoverDocumentation + productId-1: 000-001 + productId-2: 000-002 + productId-3: 001-001 +} + +script:pre-request { + function b64EncodeUnicode(str){ + return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (m,p)=>String.fromCharCode('0x'+p))); + } + + const varsToEncode = [ + 'aasIdentifier-1', + 'aasIdentifier-2', + 'aasIdentifier-3', + 'submodelIdentifierContact-1', + 'submodelIdentifierNameplate-1', + 'submodelIdentifierHandoverDocumentation-1', + 'submodelIdentifierTechnicalData-1', + 'submodelIdentifierCarbonFootprint-1' + ]; + + varsToEncode.forEach(name => { + const plain = bru.getCollectionVar(name); + const encoded = b64EncodeUnicode(plain); + bru.setVar(name, encoded); + }); + + +} diff --git a/example/apiCollection/environments/local.bru b/example/apiCollection/environments/local.bru new file mode 100644 index 0000000..5987017 --- /dev/null +++ b/example/apiCollection/environments/local.bru @@ -0,0 +1,3 @@ +vars { + DataEngineBaseUrl: http://localhost:8080 +} diff --git a/example/basyx/aas-env.properties b/example/basyx/aas-env.properties new file mode 100644 index 0000000..70ef7e2 --- /dev/null +++ b/example/basyx/aas-env.properties @@ -0,0 +1,16 @@ +server.port=8081 +basyx.environment=file:aas +basyx.cors.allowed-origins=* +basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD +basyx.aasrepository.feature.registryintegration=http://aas-template-registry:8080 +basyx.submodelrepository.feature.registryintegration=http://sm-template-registry:8080 +spring.servlet.multipart.max-file-size=128MB +spring.servlet.multipart.max-request-size=128MB +basyx.backend=MongoDB +spring.data.mongodb.host=mongo +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aasenvironment +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword + diff --git a/example/data/checkmark.png b/example/data/checkmark.png new file mode 100644 index 0000000..971aaff Binary files /dev/null and b/example/data/checkmark.png differ diff --git a/example/data/dummy_document.jpg b/example/data/dummy_document.jpg new file mode 100644 index 0000000..f6badb5 Binary files /dev/null and b/example/data/dummy_document.jpg differ diff --git a/example/data/dummy_document.pdf b/example/data/dummy_document.pdf new file mode 100644 index 0000000..f92a169 Binary files /dev/null and b/example/data/dummy_document.pdf differ diff --git a/example/data/product1.jpg b/example/data/product1.jpg new file mode 100644 index 0000000..45ecc8c Binary files /dev/null and b/example/data/product1.jpg differ diff --git a/example/data/product2.jpg b/example/data/product2.jpg new file mode 100644 index 0000000..184bb83 Binary files /dev/null and b/example/data/product2.jpg differ diff --git a/example/data/product3.jpg b/example/data/product3.jpg new file mode 100644 index 0000000..9ff957c Binary files /dev/null and b/example/data/product3.jpg differ diff --git a/example/docker-compose.yml b/example/docker-compose.yml new file mode 100644 index 0000000..50f2797 --- /dev/null +++ b/example/docker-compose.yml @@ -0,0 +1,222 @@ +networks: + twinengine-network: + name: twinengine-network + +services: + nginx: + image: nginx:trixie-perl + container_name: nginx + ports: + - "8080:80" + volumes: + - ./nginx/html:/usr/share/nginx/html + - ./nginx/default.conf.template:/etc/nginx/templates/default.conf.template + restart: always + depends_on: + aas-template-registry: + condition: service_healthy + sm-template-registry: + condition: service_healthy + template-repository: + condition: service_healthy + networks: + - twinengine-network + + twinengine-dataengine: + image: ghcr.io/aas-twinengine/dataengine:v1.0.0 + container_name: twinengine-dataengine + depends_on: + dpp-plugin: + condition: service_started + restart: always + environment: + - AasEnvironment__DataEngineRepositoryBaseUrl=http://localhost:8080 + - AasEnvironment__AasEnvironmentRepositoryBaseUrl=http://template-repository:8081 + - AasEnvironment__SubModelRepositoryPath=submodels + - AasEnvironment__AasRegistryBaseUrl=http://aas-template-registry:8080 + - AasEnvironment__AasRegistryPath=shell-descriptors + - AasEnvironment__SubModelRegistryBaseUrl=http://sm-template-registry:8080 + - AasEnvironment__SubModelRegistryPath=submodel-descriptors + - AasEnvironment__AasRepositoryPath=shells + - AasEnvironment__SubmodelRefPath=submodel-refs + - AasEnvironment__CustomerDomainUrl=https://mm-software.com + - TemplateMappingRules__SubmodelTemplateMappings__0__templateId=https://admin-shell.io/ZVEI/TechnicalData/Submodel/1/2 + - TemplateMappingRules__SubmodelTemplateMappings__0__pattern__0=TechnicalData + - TemplateMappingRules__SubmodelTemplateMappings__1__templateId=https://admin-shell.io/idta/SubmodelTemplate/DigitalNameplate/3/0 + - TemplateMappingRules__SubmodelTemplateMappings__1__pattern__0=Nameplate + - TemplateMappingRules__SubmodelTemplateMappings__2__templateId=https://admin-shell.io/idta/SubmodelTemplate/ContactInformation/1/0 + - TemplateMappingRules__SubmodelTemplateMappings__2__pattern__0=ContactInformation + - TemplateMappingRules__SubmodelTemplateMappings__3__templateId=https://admin-shell.io/idta/SubmodelTemplate/CarbonFootprint/1/0 + - TemplateMappingRules__SubmodelTemplateMappings__3__pattern__0=CarbonFootprint + - TemplateMappingRules__SubmodelTemplateMappings__4__templateId=https://admin-shell.io/idta/SubmodelTemplate/HandoverDocumentation/2/0 + - TemplateMappingRules__SubmodelTemplateMappings__4__pattern__0=HandoverDocumentation + - TemplateMappingRules__ShellTemplateMappings__0__templateId=https://mm-software.com/aas/aasTemplate + - TemplateMappingRules__ShellTemplateMappings__0__pattern__0= + - TemplateMappingRules__AasIdExtractionRules__0__pattern=Regex + - TemplateMappingRules__AasIdExtractionRules__0__index=6 + - TemplateMappingRules__AasIdExtractionRules__0__separator=/ + - Semantics__MultiLanguageSemanticPostfixSeparator=_ + - Semantics__SubmodelElementIndexContextPrefix=_aastwinengineindex_ + - Semantics__InternalSemanticId=InternalSemanticId + - AasxOptions__RootFolder=aasx + - AasRegistryPreComputed__ShellDescriptorCron=0 */3 * * * * + - AasRegistryPreComputed__IsPreComputed=false + - MultiPluginConflictOption__HandlingMode=TakeFirst + - PluginConfig__Plugins__0__PluginName=Plugin1 + - PluginConfig__Plugins__0__PluginUrl=http://dpp-plugin:8080 + networks: + - twinengine-network + + dpp-plugin: + image: ghcr.io/aas-twinengine/plugindpp:v1.0.0 + container_name: dpp-plugin + depends_on: + postgres: + condition: service_healthy + restart: always + environment: + - Semantics__IndexContextPrefix=_aastwinengineindex_ + - Capabilities__HasShellDescriptor=true + - Capabilities__HasAssetInformation=true + - RelationalDatabaseConfiguration__ConnectionString=Host=postgres;Port=5432;Database=twinengine;Username=postgres;Password=admin + - ExtractionRules__SubmodelNameExtractionRules__0__SubmodelName=NamePlate + - ExtractionRules__SubmodelNameExtractionRules__0__pattern__0=Nameplate + - ExtractionRules__SubmodelNameExtractionRules__0__pattern__1=NamePlate + - ExtractionRules__SubmodelNameExtractionRules__1__SubmodelName=ContactInformation + - ExtractionRules__SubmodelNameExtractionRules__1__pattern__0=ContactInformation + - ExtractionRules__SubmodelNameExtractionRules__1__pattern__1=ContactInformations + - ExtractionRules__SubmodelNameExtractionRules__2__SubmodelName=HandoverDocumentation + - ExtractionRules__SubmodelNameExtractionRules__2__pattern__0=HandoverDocumentation + - ExtractionRules__SubmodelNameExtractionRules__2__pattern__1=HandoverDocumentations + - ExtractionRules__SubmodelNameExtractionRules__3__SubmodelName=TechnicalData + - ExtractionRules__SubmodelNameExtractionRules__3__pattern__0=TechnicalData + - ExtractionRules__SubmodelNameExtractionRules__4__SubmodelName=CarbonFootprint + - ExtractionRules__SubmodelNameExtractionRules__4__pattern__0=CarbonFootprint + - ExtractionRules__SubmodelNameExtractionRules__4__pattern__1=Footprint + - ExtractionRules__SubmodelNameExtractionRules__4__pattern__2=Carbon + - ExtractionRules__ProductIdExtractionRules__0__Pattern=Regex + - ExtractionRules__ProductIdExtractionRules__0__Index=5 + - ExtractionRules__ProductIdExtractionRules__0__Separator=/ + networks: + - twinengine-network + + template-repository: + image: eclipsebasyx/aas-environment:2.0.0-SNAPSHOT + container_name: template-repository + volumes: + - ./aas:/application/aas + - ./basyx/aas-env.properties:/application/application.properties + environment: + BASYX_EXTERNALURL : http://localhost:8080 + restart: always + depends_on: + aas-template-registry: + condition: service_healthy + sm-template-registry: + condition: service_healthy + networks: + - twinengine-network + + aas-template-registry: + image: eclipsebasyx/aas-registry-log-mongodb:2.0.0-SNAPSHOT + container_name: aas-template-registry + restart: always + depends_on: + - mongo + environment: + - SPRING_DATA_MONGODB_URI=mongodb://mongoAdmin:mongoPassword@mongo:27017 + networks: + - twinengine-network + + sm-template-registry: + image: eclipsebasyx/submodel-registry-log-mongodb:2.0.0-SNAPSHOT + container_name: sm-template-registry + depends_on: + - mongo + environment: + - SPRING_DATA_MONGODB_URI=mongodb://mongoAdmin:mongoPassword@mongo:27017 + networks: + - twinengine-network + + shell-template-creator: + image: curlimages/curl:8.18.0 + container_name: shell-template-creator + depends_on: + template-repository: + condition: service_healthy + entrypoint: > + sh -c ' + echo "Waiting for template-repository to be ready..."; + sleep 10; + curl -X POST http://template-repository:8081/shells \ + -H "accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"id\":\"https://mm-software.com/aas/aasTemplate\",\"assetInformation\":{\"assetKind\":\"Instance\"},\"submodels\":[{\"type\":\"ModelReference\",\"keys\":[{\"type\":\"Submodel\",\"value\":\"Nameplate\"}]},{\"type\":\"ModelReference\",\"keys\":[{\"type\":\"Submodel\",\"value\":\"ContactInformation\"}]},{\"type\":\"ModelReference\",\"keys\":[{\"type\":\"Submodel\",\"value\":\"TechnicalData\"}]},{\"type\":\"ModelReference\",\"keys\":[{\"type\":\"Submodel\",\"value\":\"CarbonFootprint\"}]},{\"type\":\"ModelReference\",\"keys\":[{\"type\":\"Submodel\",\"value\":\"HandoverDocumentation\"}]}],\"modelType\":\"AssetAdministrationShell\"}"; + echo "Shell creation request sent."; + ' + networks: + - twinengine-network + + aas-web-ui: + image: eclipsebasyx/aas-gui:SNAPSHOT + container_name: aas-ui + volumes: + - ./logo:/usr/src/app/dist/Logo + environment: + AAS_REGISTRY_PATH: http://localhost:8080/shell-descriptors + SUBMODEL_REGISTRY_PATH: http://localhost:8080/submodel-descriptors + AAS_REPO_PATH: http://localhost:8080/shells + SUBMODEL_REPO_PATH: http://localhost:8080/submodels + CD_REPO_PATH: http://localhost:8080/concept-descriptions + BASE_PATH: "/aas-ui" + LOGO_PATH: "MM_Logo.svg" + PRIMARY_DARK_COLOR: "#00F2E5" + PRIMARY_LIGHT_COLOR: "#041b2b" + restart: always + depends_on: + template-repository: + condition: service_healthy + networks: + - twinengine-network + + mongo: + image: mongo:6.0 + container_name: mongo + environment: + MONGO_INITDB_ROOT_USERNAME: mongoAdmin + MONGO_INITDB_ROOT_PASSWORD: mongoPassword + networks: + - twinengine-network + + postgres: + image: postgres:16-alpine + container_name: postgres + environment: + POSTGRES_DB: twinengine + POSTGRES_USER: postgres + POSTGRES_PASSWORD: admin + volumes: + - ./postgres:/docker-entrypoint-initdb.d + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d twinengine"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - twinengine-network + + pgadmin: + image: dpage/pgadmin4:snapshot + container_name: pgadmin + ports: + - "8081:80" + environment: + PGADMIN_DEFAULT_EMAIL: admin@example.com + PGADMIN_DEFAULT_PASSWORD: admin + depends_on: + postgres: + condition: service_healthy + restart: always + networks: + - twinengine-network diff --git a/example/logo/MM_Logo.svg b/example/logo/MM_Logo.svg new file mode 100644 index 0000000..35fe73f --- /dev/null +++ b/example/logo/MM_Logo.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/example/nginx/default.conf.template b/example/nginx/default.conf.template new file mode 100644 index 0000000..d3fadf5 --- /dev/null +++ b/example/nginx/default.conf.template @@ -0,0 +1,133 @@ +server { + listen 80; + server_name _; + + # Serve static files + location /fileprovider/ { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + root /usr/share/nginx/html; + } + + location /aas-ui/ { + proxy_pass http://aas-web-ui:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Authorization $http_authorization; + proxy_set_header Accept $http_accept; + proxy_set_header Content-Type $http_content_type; + } + + + location /shell-descriptors { + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /shells { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /submodels { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /concept-descriptions/ { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://template-repository:8081; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /concept-descriptions { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://template-repository:8081; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /submodel-descriptors/ { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + } + + location /submodel-descriptors { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /serialization { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /description { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://sm-template-registry:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/example/postgres/01_core_asset_tables.sql.inc b/example/postgres/01_core_asset_tables.sql.inc new file mode 100644 index 0000000..6b983ca --- /dev/null +++ b/example/postgres/01_core_asset_tables.sql.inc @@ -0,0 +1,118 @@ +-- ============================================================ +-- Core Asset Tables +-- ============================================================ + +CREATE TABLE "Asset" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "ProductId" VARCHAR(50), + "IdShort" VARCHAR(100), + "GlobalAssetId" TEXT, + "AasId" TEXT, + "ThumbnailContentType" VARCHAR(50), + "ThumbnailPath" TEXT, + "UriOfTheProduct" TEXT, + "ManufacturerProductType" TEXT, + "OrderCodeOfManufacturer" TEXT, + "ProductArticleNumberOfManufacturer" TEXT, + "SerialNumber" TEXT, + "YearOfConstruction" INT, + "DateOfManufacture" DATE, + "HardwareVersion" TEXT, + "FirmwareVersion" TEXT, + "SoftwareVersion" TEXT, + "CountryOfOrigin" TEXT, + "UniqueFacilityIdentifier" TEXT, + "ManufacturerName" TEXT, + "ManufacturerProductDesignation_en" TEXT, + "ManufacturerProductDesignation_de" TEXT, + "ManufacturerProductRoot_en" TEXT, + "ManufacturerProductRoot_de" TEXT, + "ManufacturerProductFamily_en" TEXT, + "ManufacturerProductFamily_de" TEXT, + "CompanyLogo" TEXT, + "ManufacturerArticleNumber" TEXT, + "ManufacturerOrderCode" TEXT, + "ProductImage" TEXT, + "ManufacturerLogo" TEXT, + "TextStatement_en" TEXT, + "TextStatement_de" TEXT, + "ValidDate" DATE, + "PcfCalculationMethod" TEXT, + "LifeCyclePhase" TEXT, + "PcfCO2eq" NUMERIC, + "ReferenceImpactUnitForCalculation" TEXT, + "QuantityOfMeasureForCalculation" NUMERIC, + "PublicationDate" TIMESTAMP, + "ExpirationDate" TIMESTAMP, + "ExplanatoryStatement" TEXT +); + +CREATE TABLE "SpecificAssetIds" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "AssetId" INT NOT NULL REFERENCES "Asset"("Id") ON DELETE CASCADE, + "Name" TEXT, + "Value" TEXT +); + +INSERT INTO "Asset" ( + "ProductId","IdShort","GlobalAssetId","AasId","ThumbnailContentType","ThumbnailPath", + "UriOfTheProduct","ManufacturerProductType","OrderCodeOfManufacturer","ProductArticleNumberOfManufacturer", + "SerialNumber","YearOfConstruction","DateOfManufacture","HardwareVersion","FirmwareVersion","SoftwareVersion", + "CountryOfOrigin","UniqueFacilityIdentifier","ManufacturerName","ManufacturerProductDesignation_en", + "ManufacturerProductDesignation_de","ManufacturerProductRoot_en","ManufacturerProductRoot_de", + "ManufacturerProductFamily_en","ManufacturerProductFamily_de","CompanyLogo","ManufacturerArticleNumber", + "ManufacturerOrderCode","ProductImage","ManufacturerLogo","TextStatement_en","TextStatement_de", + "ValidDate","PcfCalculationMethod","LifeCyclePhase","PcfCO2eq","ReferenceImpactUnitForCalculation", + "QuantityOfMeasureForCalculation","PublicationDate","ExpirationDate","ExplanatoryStatement" +) VALUES +('000-001','Product1','https://mm-software.com/ids/assets/000-001','https://mm-software.com/ids/aas/000-001','image/jpeg', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/product1.jpg', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/product1.jpg', + 'FM-ABC-1234','FMABC1234','FM11-ABC22-123456','9804820',2022,'2022-01-01', + '1.0.0','1.0.0','1.0.0','DE','987654321','M&M Germany', + 'FM-ABC-1234','ABC-123','Camera','Kamera','Electronics','Elektronik', + 'https://mmsoftwaregmbh.sharepoint.com/_api/siteiconmanager/getsitelogo?type=%271%27&hash=638518734598723853', + '123456','EEA-EX-200-S/47-Q3', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/product1.jpg', + 'https://mmsoftwaregmbh.sharepoint.com/_api/siteiconmanager/getsitelogo?type=%271%27&hash=638518734598723853', + 'Restricted use','Eingeschränkte Nutzung','2035-05-05', + 'ISO 14067','C4 - landfill',17.2,'ml',5, + '2025-12-24T14:30:00Z','2035-12-24T14:30:00Z', + 'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf'), +('000-002','Product2','https://mm-software.com/ids/assets/000-002','https://mm-software.com/ids/aas/000-002','image/jpeg', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/product2.jpg', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/product2.jpg', + 'FM-ABC-1235','FMABC1238','FM11-ABC22-123458','9804821',2023,'2023-01-01', + '1.0.1','1.0.1','1.0.1','IN','123567890','M&M India', + 'FM-ABC-123','ABC-123','Camera','Kamera','Electronics','Elektronik', + 'https://mmsoftwaregmbh.sharepoint.com/_api/siteiconmanager/getsitelogo?type=%271%27&hash=638518734598723853', + '123456','EEA-EX-200-S/47-Q4', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/product2.jpg', + 'https://mmsoftwaregmbh.sharepoint.com/_api/siteiconmanager/getsitelogo?type=%271%27&hash=638518734598723853', + 'Restricted use','Eingeschränkte Nutzung','2035-06-05', + 'EN 15804','A5 - Installation',6.2,'cbm',2.2999999999999998, + '2026-01-15T09:15:00+05:30','2036-01-15T09:15:00+05:31', + 'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf'), +('001-001','Product3','https://mm-software.com/ids/assets/001-001','https://mm-software.com/ids/aas/001-001','image/jpeg', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/product3.jpg', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/product3.jpg', + 'TM-ABC-1680','TMABC1680','TM11-ABC22-123456','9804760',2024,'2024-01-01', + '2.0.0','1.0.0','1.0.2','CN','874512451','M&M China', + 'TM-ABC-1234','ABC-1234','perfume','Parfüm','Cosmetics','Kosmetika', + 'https://mmsoftwaregmbh.sharepoint.com/_api/siteiconmanager/getsitelogo?type=%271%27&hash=638518734598723853', + '123456','EEA-EX-200-S/47-Q5', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/product3.jpg', + 'https://mmsoftwaregmbh.sharepoint.com/_api/siteiconmanager/getsitelogo?type=%271%27&hash=638518734598723853', + 'Restricted use','Eingeschränkte Nutzung','2035-07-05', + 'PACT v2.0.0','C3 - recycling, waste treatment',2.2999999999999998,'piece',7.8, + '2024-07-01T18:45:00-04:00','2034-07-01T18:45:00-04:01', + 'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf'); + +-- ============================================================ +-- Specific Asset IDs +-- ============================================================ + +INSERT INTO "SpecificAssetIds" ("AssetId","Name","Value") VALUES +(1,'product1.0','M&M - 001'), +(1,'product1.1','M&M - 002'), +(2,'product2','M&M - 003'); diff --git a/example/postgres/02_nameplate_carbonfootprint_technicaldata.sql.inc b/example/postgres/02_nameplate_carbonfootprint_technicaldata.sql.inc new file mode 100644 index 0000000..a0806d5 --- /dev/null +++ b/example/postgres/02_nameplate_carbonfootprint_technicaldata.sql.inc @@ -0,0 +1,116 @@ +-- ============================================================ +-- Core Data Insertion +-- ============================================================ +-- This file contains CREATE and INSERT statements for core business entities for NamePlate Submodel: +-- - Markings +-- - Asset-Marking relationships +-- - Product Classification Items +-- - Carbon Footprint data +-- -Asset-ProductClassificationItem relationships +-- ============================================================ + +CREATE TABLE "Marking" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "Index" INT, + "MarkingName" TEXT, + "DesignationOfCertificateOrApproval" TEXT, + "IssueDate" DATE, + "ExpiryDate" DATE, + "MarkingAdditionalText" TEXT, + "MarkingFile" TEXT +); + +CREATE TABLE "AssetMarking" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "AssetId" INT NOT NULL REFERENCES "Asset"("Id") ON DELETE CASCADE, + "MarkingId" INT NOT NULL REFERENCES "Marking"("Id") ON DELETE CASCADE +); + +-- ============================================================ +-- Product Classification Tables +-- ============================================================ + +CREATE TABLE "ProductClassificationItem" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "Index" INT, + "ProductClassificationSystem" TEXT, + "ClassificationSystemVersion" TEXT, + "ProductClassId" TEXT +); + +CREATE TABLE "AssetProductClassificationItem" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "AssetId" INT REFERENCES "Asset"("Id") ON DELETE CASCADE, + "ProductClassificationItemId" INT REFERENCES "ProductClassificationItem"("Id") ON DELETE CASCADE +); + +CREATE TABLE "ProductOrSectorSpecificCarbonFootprint" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "AssetId" INT REFERENCES "Asset"("Id") ON DELETE CASCADE, + "PcfCalculationMethod" TEXT, + "PcfRuleOperator" TEXT, + "PcfRuleName" TEXT, + "PcfRuleVersion" TEXT, + "PcfRuleOnlineReference" TEXT, + "PcfApiEndpoint" TEXT, + "PcfApiQuery" TEXT +); + +-- ============================================================ +-- Marking Data +-- ============================================================ + +INSERT INTO "Marking" ("Index","MarkingName","DesignationOfCertificateOrApproval","IssueDate","ExpiryDate","MarkingAdditionalText","MarkingFile") VALUES +(0,'0173-1#07-DAA603#004','KEMA99IECEX1105/128','2022-01-01','2030-01-01','additional information on the marking - 00', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/checkmark.png'), +(0,'0173-1#07-DAA603#005','KEMA99IECEX1105/129','2022-02-01','2030-02-01','additional information on the marking - 01', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/checkmark.png'), +(1,'0173-1#07-DAA603#006','KEMA99IECEX1105/130','2022-03-01','2030-03-01','additional information on the marking - 02', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/checkmark.png'), +(0,'0173-1#07-DAA603#007','KEMA99IECEX1105/131','2022-04-01','2030-04-01','additional information on the marking - 03', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/checkmark.png'), +(1,'0173-1#07-DAA603#008','KEMA99IECEX1105/132','2022-05-01','2030-05-01','additional information on the marking - 04', + 'https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/checkmark.png'); + +-- ============================================================ +-- Asset-Marking Relationships +-- ============================================================ + +INSERT INTO "AssetMarking" ("AssetId","MarkingId") VALUES +(1,1), +(1,3), +(2,2), +(2,3), +(3,4), +(3,5); + +-- ============================================================ +-- Product Classification +-- ============================================================ + +INSERT INTO "ProductClassificationItem" ("Index","ProductClassificationSystem","ClassificationSystemVersion","ProductClassId") VALUES +(0,'ECLASS','14','19-01-01-01'), +(1,'IEC CDD','2024-09','IEC-CDD-AAA124'), +(2,'UNSPSC','23.0301','UNSPSC-43211503'), +(0,'ISO 13584','2023','ISO13584-XYZ789'), +(0,'ECLASS','5','27-02-03-05'), +(1,'IEC CDD','2024-09','IEC-CDD-AAA123'); + +INSERT INTO "AssetProductClassificationItem" ("AssetId","ProductClassificationItemId") VALUES +(1,1), +(1,2), +(1,3), +(2,4), +(3,5), +(3,6); + +-- ============================================================ +-- Carbon Footprint Data +-- ============================================================ + +INSERT INTO "ProductOrSectorSpecificCarbonFootprint" ( + "AssetId","PcfCalculationMethod","PcfRuleOperator","PcfRuleName","PcfRuleVersion","PcfRuleOnlineReference","PcfApiEndpoint","PcfApiQuery" +) VALUES +(1,'IEC TS 63058','GHG Protocol','GHG Protocol Product Standard','1.1','https://ghgprotocol.org/standards/product-standard','https://api.carbonfootprint.org/v1/calculate','?productId=12345&unit=kgCO2e&scope=cradle-to-gate'), +(2,'EN 15804','ISO 14067','ISO 14067','2.1','https://www.iso.org/standard/43278.html','https://api.iso14067.org/v2/emissions','?sector=electronics®ion=EU&year=2025'), +(3,'PACT v2.0.0','PAS 2050','PAS 2050','0.9','https://www.bsigroup.com/en-GB/PAS-2050-Carbon-Footprint/','https://api.pas2050.com/v1/footprint','?material=steel&quantity=1000kg&method=ISO14067'); diff --git a/example/postgres/03_contactinformations.sql.inc b/example/postgres/03_contactinformations.sql.inc new file mode 100644 index 0000000..9cb8af6 --- /dev/null +++ b/example/postgres/03_contactinformations.sql.inc @@ -0,0 +1,221 @@ +-- ============================================================ +-- Contact Information Data Insertion +-- ============================================================ +-- This file contains CREATE And INSERT statements for contact-related entities: +-- - ContactInformation +-- - Phone numbers +-- - Fax numbers +-- - Email addresses +-- - IP Communication channels +-- - Asset-ContactInformation relationships +-- ============================================================ +-- ============================================================ +-- Contact Information Tables +-- ============================================================ + +CREATE TABLE "ContactInformation" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "Index" INT, + "RoleOfContactPerson" TEXT, + "Language" TEXT, + "TimeZone" TEXT, + "AddressOfAdditionalLink" TEXT, + "NationalCode_en" TEXT, + "NationalCode_de" TEXT, + "CityTown_en" TEXT, + "CityTown_de" TEXT, + "Company_en" TEXT, + "Company_de" TEXT, + "Department_en" TEXT, + "Department_de" TEXT, + "Street_en" TEXT, + "Street_de" TEXT, + "Zipcode_en" TEXT, + "Zipcode_de" TEXT, + "POBox_en" TEXT, + "POBox_de" TEXT, + "ZipCodeOfPOBox_en" TEXT, + "ZipCodeOfPOBox_de" TEXT, + "StateCounty_en" TEXT, + "StateCounty_de" TEXT, + "NameOfContact_en" TEXT, + "NameOfContact_de" TEXT, + "FirstName_en" TEXT, + "FirstName_de" TEXT, + "MiddleNames_en" TEXT, + "MiddleNames_de" TEXT, + "Title_en" TEXT, + "Title_de" TEXT, + "AcademicTitle_en" TEXT, + "AcademicTitle_de" TEXT, + "FurtherDetailsOfContact_en" TEXT, + "FurtherDetailsOfContact_de" TEXT +); + +CREATE TABLE "AssetContactInformation" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "AssetId" INT NOT NULL REFERENCES "Asset"("Id") ON DELETE CASCADE, + "ContactInformationId" INT NOT NULL REFERENCES "ContactInformation"("Id") ON DELETE CASCADE +); + +CREATE TABLE "Phone" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "ContactInformationId" INT REFERENCES "ContactInformation"("Id") ON DELETE CASCADE, + "TelephoneNumber_en" TEXT, + "TelephoneNumber_de" TEXT, + "AvailableTime_en" TEXT, + "AvailableTime_de" TEXT, + "TypeOfTelephone" TEXT +); + +CREATE TABLE "Fax" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "ContactInformationId" INT REFERENCES "ContactInformation"("Id") ON DELETE CASCADE, + "FaxNumber_en" TEXT, + "FaxNumber_de" TEXT, + "TypeOfFaxNumber" TEXT +); + +CREATE TABLE "Email" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "ContactInformationId" INT REFERENCES "ContactInformation"("Id") ON DELETE CASCADE, + "EmailAddress" TEXT, + "TypeOfEmailAddress" TEXT, + "PublicKey_en" TEXT, + "PublicKey_de" TEXT, + "TypeOfPublicKey_en" TEXT, + "TypeOfPublicKey_de" TEXT +); + +CREATE TABLE "IPCommunication" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "Index" INT, + "AddressOfAdditionalLink" TEXT, + "TypeOfCommunication" TEXT, + "AvailableTime_en" TEXT, + "AvailableTime_de" TEXT +); + +CREATE TABLE "ContactInformationIPCommunication" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "ContactInformationId" INT REFERENCES "ContactInformation"("Id") ON DELETE CASCADE, + "IPCommunicationId" INT REFERENCES "IPCommunication"("Id") ON DELETE CASCADE +); + + +-- ============================================================ +-- Contact Information +-- ============================================================ + +INSERT INTO "ContactInformation" ( + "Index","RoleOfContactPerson","Language","TimeZone","AddressOfAdditionalLink", + "NationalCode_en","NationalCode_de","CityTown_en","CityTown_de","Company_en","Company_de", + "Department_en","Department_de","Street_en","Street_de","Zipcode_en","Zipcode_de","POBox_en","POBox_de", + "ZipCodeOfPOBox_en","ZipCodeOfPOBox_de","StateCounty_en","StateCounty_de","NameOfContact_en","NameOfContact_de", + "FirstName_en","FirstName_de","MiddleNames_en","MiddleNames_de","Title_en","Title_de","AcademicTitle_en","AcademicTitle_de", + "FurtherDetailsOfContact_en","FurtherDetailsOfContact_de" +) VALUES +(0,'0173-1#07-AAS927#001','EN','Z+05:30','https://www.mm-software.com/','IN','IN','Mumbai','München', + 'M&M India','M&M India','Human Resources','Human Resources', + '221B Baker Street','221B Baker Street','110001','110001','PO Box 123','PO Box 124','110002','110002', + 'Delhi','Delhi','Aarav Sharma','Aarav Sharma','Aarav','Aarav','Sharma','Sharma','Mr.','Herr','Dr.','Dr.', + 'Responsible for B2B sales in region-1','Responsible for B2B sales in region-1'), +(1,'0173-1#07-AAS928#001','DE','Z+01:00','https://www.mm-software.com/we/','DE','DE','Berlin','Berlin', + 'M&M Germany','M&M Germany','Finance','Finance','Hauptstraße 15','Hauptstraße 16','10115','10115','Postfach 789','Postfach 790', + '10116','10116','Berlin','Berlin','Lukas Mueller','Lukas Müller','Lukas','Lukas','Müller','Mueller','Mr.','Herr','Dr.','Dr.', + 'Responsible for B2B sales in region-2','Responsible for B2B sales in region-2'), +(0,'0173-1#07-AAS931#001','EN','Z+05:30','https://www.mm-software.com/more-the-newsroom/detail/mm-software-auf-der-sps-2025/','IN','IN','Mumbai','München', + 'M&M India','M&M India','Operations','Operations', + '221B Baker Street','221B Baker Street','110001','110001','PO Box 123','PO Box 124','110002','110002','Delhi','Delhi', + 'Priya Mehta','Priya Mehta','Priya','Priya','Mehta','Mehta','Ms','Frau','Prof.','Prof.','Responsible for B2B sales in region-3','Responsible for B2B sales in region-3'), +(1,'0173-1#07-AAS930#001','DE','Z+01:00','https://www.mm-software.com/more-the-newsroom/detail/entscheide-dich-teil-2/','DE','DE','Berlin','Berlin', + 'M&M Germany','M&M Germany','Human Resources','Human Resources','Hauptstraße 15','Hauptstraße 16','10115','10115','Postfach 789','Postfach 790', + '10116','10116','Berlin','Berlin','Anna Schneider','Anna Schneider','Anna','Anna','Schneider','Schneider','Ms','Frau','Prof.','Prof.','Responsible for B2B sales in region-4','Responsible for B2B sales in region-4'), +(2,'0173-1#07-AAS929#001','FR','Z+08:00','https://www.mm-software.com/more-the-newsroom/detail/digitaler-produktpass-dpp-in-der-praxis-leitfaden-und-checkliste-fuer-unternehmen/','CN','CN','Beijing','Beijing', + 'M&M China','M&M China','Finance','Finance','88 Nanjing Road','89 Nanjing Road','200001','200001','P.O. Box 456','P.O. Box 457', + '200002','200002','hanghai Municipality','hanghai Municipality','Anna Müller','Wei Li','Li','Li','Wei','Wei','Mr.','Herr','Dr.','Dr.', + 'Responsible for B2B sales in region-5','Responsible for B2B sales in region-5'), +(3,'0173-1#07-AAS928#001','DE','Z+01:00','https://www.mm-software.com/more-the-newsroom/','DE','DE','Berlin','Berlin', + 'M&M Germany','M&M Germany','Operations','Operations','Hauptstraße 15','Hauptstraße 16','10115','10115','Postfach 789','Postfach 790', + '10116','10116','Berlin','Berlin','Johann Becker','Johann Becker','Johann','Johann','Becker','Becker','Ms','Frau','Prof.','Prof.', + 'Responsible for B2B sales in region-6','Responsible for B2B sales in region-6'); + +-- ============================================================ +-- Phone Numbers +-- ============================================================ + +INSERT INTO "Phone" ("ContactInformationId","TelephoneNumber_en","TelephoneNumber_de","AvailableTime_en","AvailableTime_de","TypeOfTelephone") VALUES +(1,'+49 151 23456789','+49 151 23456790','Monday – Friday 08:00 to 16:00','Montag – Freitag 08:00 bis 16:00','0173-1#07-AAS754#001'), +(2,'+91 98765 43210','+91 98765 43211','Monday – Thursday 09:00 to 17:00','Montag – Donnerstag 09:00 bis 17:00','0173-1#07-AAS755#001'), +(3,'+49 160 98765432','+49 160 98765433','Tuesday – Saturday 07:30 to 15:30','Dienstag – Samstag 07:30 bis 15:30','0173-1#07-AAS756#001'), +(4,'+91 91234 56789','+91 91234 56790','Monday – Friday 10:00 to 18:00','Montag – Freitag 10:00 bis 18:00','0173-1#07-AAS757#001'), +(5,'+86 138 1234 5678','+86 138 1234 5679','Wednesday – Sunday 08:00 to 14:00','Mittwoch – Sonntag 08:00 bis 14:00','0173-1#07-AAS758#001'), +(6,'+49 170 12345678','+49 170 12345679','Monday – Friday 06:00 to 12:00','Montag – Freitag 06:00 bis 12:00','0173-1#07-AAS759#001'); + +-- ============================================================ +-- Fax Numbers +-- ============================================================ + +INSERT INTO "Fax" ("ContactInformationId","FaxNumber_en","FaxNumber_de","TypeOfFaxNumber") VALUES +(1,'+49 151 23456789','+49 151 23456790','0173-1#07-AAS754#001'), +(2,'+91 98765 43210','+91 98765 43211','0173-1#07 AAS756#001'), +(3,'+49 160 98765432','+49 160 98765433','0173-1#07-AAS756#001'), +(4,'+91 91234 56789','+91 91234 56790','0173-1#07-AAS754#002'), +(5,'+86 138 1234 5678','+86 138 1234 5679','0173-1#07 AAS756#002'), +(6,'+49 170 12345678','+49 170 12345679','0173-1#07-AAS756#002'); + +-- ============================================================ +-- Email Addresses +-- ============================================================ + +INSERT INTO "Email" ("ContactInformationId","EmailAddress","TypeOfEmailAddress","PublicKey_en","PublicKey_de","TypeOfPublicKey_en","TypeOfPublicKey_de") VALUES +(1,'aarav.sharma@example.in','0173-1#07-AAS754#001','A1B2C3D4E5F67890ABCDEF1234567890ABCDEF12','A1B2C3D4E5F67890ABCDEF1234567890ABCDEF13','RSA Encryption','RSA-Verschlüsselung'), +(2,'lukas.mueller@example.de','0173-1#07-AAS756#001','B2C3D4E5F67890ABCDEF1234567890ABCDEF1234','B2C3D4E5F67890ABCDEF1234567890ABCDEF1235','ECC Encryption','ECC-Verschlüsselung'), +(3,'priya.mehta@example.in','0173-1#07-AAS757#001','C3D4E5F67890ABCDEF1234567890ABCDEF123456','C3D4E5F67890ABCDEF1234567890ABCDEF123457','DSA Signature','DSA-Signatur'), +(4,'anna.schneider@example.de','0173-1#07-AAS758#001','E5F67890ABCDEF1234567890ABCDEF1234567890','E5F67890ABCDEF1234567890ABCDEF1234567891','EdDSA Signature','EdDSA-Signatur'), +(5,'wei.li@example.cn','0173-1#07-AAS754#001','F67890ABCDEF1234567890ABCDEF1234567890AB','F67890ABCDEF1234567890ABCDEF1234567890AB','RSA Encryption','RSA-Verschlüsselung'), +(6,'johann.becker@example.de','0173-1#07-AAS756#001','D4E5F67890ABCDEF1234567890ABCDEF12345678','D4E5F67890ABCDEF1234567890ABCDEF12345679','ECC Encryption','ECC-Verschlüsselung'); + +-- ============================================================ +-- IP Communication Channels +-- ============================================================ + +INSERT INTO "IPCommunication" ("Index","AddressOfAdditionalLink","TypeOfCommunication","AvailableTime_en","AvailableTime_de") VALUES +(0,'https://www.mm-software.com/more-the-newsroom/','Chat','Monday – Friday 08:00 to 16:00','Montag – Freitag 08:00 bis 16:00'), +(1,'https://www.mm-software.com/more-the-newsroom/detail/digitaler-produktpass-dpp-in-der-praxis-leitfaden-und-checkliste-fuer-unternehmen/','Video call','Monday – Thursday 09:00 to 17:00','Montag – Donnerstag 09:00 bis 17:00'), +(0,'https://www.mm-software.com/more-the-newsroom/','Chat','Tuesday – Saturday 07:30 to 15:30','Dienstag – Samstag 07:30 bis 15:30'), +(0,'https://www.mm-software.com/we/','Video call','Monday – Friday 10:00 to 18:00','Montag – Freitag 10:00 bis 18:00'), +(0,'https://www.mm-software.com/we/code-of-conduct/','Chat','Wednesday – Sunday 08:00 to 14:00','Mittwoch – Sonntag 08:00 bis 14:00'), +(1,'https://www.mm-software.com/more-the-newsroom/anmeldung/','Video call','Monday – Friday 06:00 to 12:00','Montag – Freitag 06:00 bis 12:00'), +(2,'https://www.mm-software.com/','Chat','Monday – Friday 08:00 to 16:00','Montag – Freitag 08:00 bis 16:00'); + +-- ============================================================ +-- Contact-IPCommunication Relationships +-- ============================================================ + +INSERT INTO "ContactInformationIPCommunication" ("ContactInformationId","IPCommunicationId") VALUES +(1,1), +(1,2), +(2,3), +(3,4), +(4,5), +(5,4), +(6,5), +(6,6), +(6,7); + +-- ============================================================ +-- Asset-ContactInformation Relationships +-- ============================================================ + +INSERT INTO "AssetContactInformation" ( + "AssetId", + "ContactInformationId" +) +VALUES + (1, 1), + (1, 2), + (2, 3), + (3, 4), + (3, 5), + (3, 6); diff --git a/example/postgres/04_handoverdocumentation.sql.inc b/example/postgres/04_handoverdocumentation.sql.inc new file mode 100644 index 0000000..14d0e89 --- /dev/null +++ b/example/postgres/04_handoverdocumentation.sql.inc @@ -0,0 +1,224 @@ +-- ============================================================ +-- Classification and Document Data Insertion +-- ============================================================ +-- This file contains CREATE and INSERT statements for: +-- - Documents and Document metadata +-- - Document Classifications +-- - Document Versions +-- - All related relationships +-- ============================================================ + +-- ============================================================ +-- Document Management Tables +-- ============================================================ + +CREATE TABLE "Document" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "Index" INT +); + +CREATE TABLE "DocumentId" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "Index" INT, + "DocumentDomainId" UUID, + "DocumentIdentifier" TEXT, + "DocumentIsPrimary" BOOLEAN +); + +CREATE TABLE "AssetDocument" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "AssetId" INT REFERENCES "Asset"("Id") ON DELETE CASCADE, + "DocumentId" INT REFERENCES "Document"("Id") ON DELETE CASCADE +); + +CREATE TABLE "DocumentDocumentId" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "DocumentId" INT REFERENCES "Document"("Id") ON DELETE CASCADE, + "DocumentIdentifierId" INT REFERENCES "DocumentId"("Id") ON DELETE CASCADE +); + +CREATE TABLE "DocumentClassification" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "Index" INT, + "ClassId" TEXT, + "ClassificationSystem" TEXT, + "ClassName_en" TEXT, + "ClassName_de" TEXT +); + +CREATE TABLE "DocumentDocumentClassification" ( + "Id" INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + "DocumentId" INT REFERENCES "Document"("Id") ON DELETE CASCADE, + "DocumentClassificationId" INT REFERENCES "DocumentClassification"("Id") ON DELETE CASCADE +); + +CREATE TABLE "DocumentVersion" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "Index" INT, + "en" TEXT, + "DigitalFile" TEXT, + "Version" TEXT, + "StatusSetDate" DATE, + "StatusValue" TEXT, + "OrganizationShortName" TEXT, + "OrganizationOfficialName" TEXT, + "Title_en" TEXT, + "Title_de" TEXT, + "Subtitle_en" TEXT, + "Subtitle_de" TEXT, + "Description_en" TEXT, + "Description_de" TEXT, + "KeyWords_en" TEXT, + "KeyWords_de" TEXT, + "PreviewFile" TEXT +); + +CREATE TABLE "DocumentDocumentVersion" ( + "Id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "DocumentId" INT REFERENCES "Document"("Id") ON DELETE CASCADE, + "DocumentVersionId" INT REFERENCES "DocumentVersion"("Id") ON DELETE CASCADE +); + +-- ============================================================ +-- Documents +-- ============================================================ + +INSERT INTO "Document" ("Index") VALUES +(0),(1),(2),(0),(1),(0),(1); + +-- ============================================================ +-- Document Identifiers +-- ============================================================ + +INSERT INTO "DocumentId" ("Index","DocumentDomainId","DocumentIdentifier","DocumentIsPrimary") VALUES +(0,'a3f1c2b4-9d81-4e5a-b6f2-01ac9e11d001','DOC2025A001',TRUE), +(1,'b7e92d10-3c45-4a8f-9f21-02bc9e11d002','CERTX94B21',FALSE), +(0,'c81a4f22-6d91-43bb-a812-03cd9e11d003','MANUAL8F2Q7',TRUE), +(0,'a3f1c2b4-9d81-4e5a-b6f2-01ac9e11d002','DOC2025A002',TRUE), +(0,'b7e92d10-3c45-4a8f-9f21-02bc9e11d003','CERTX94B22',TRUE), +(0,'c81a4f22-6d91-43bb-a812-03cd9e11d004','MANUAL8F2Q8',TRUE), +(1,'a3f1c2b4-9d81-4e5a-b6f2-01ac9e11d003','DOC2025A003',TRUE), +(2,'b7e92d10-3c45-4a8f-9f21-02bc9e11d004','CERTX94B23',FALSE), +(0,'c81a4f22-6d91-43bb-a812-03cd9e11d005','MANUAL8F2Q9',FALSE); + +-- ============================================================ +-- Asset-Document Relationships +-- ============================================================ + +INSERT INTO "AssetDocument" ("AssetId","DocumentId") VALUES +(1,1), +(1,2), +(1,3), +(2,4), +(2,5), +(3,6), +(3,7); + +-- ============================================================ +-- Document-DocumentId Relationships +-- ============================================================ + +INSERT INTO "DocumentDocumentId" ("DocumentId","DocumentIdentifierId") VALUES +(1,1), +(1,2), +(2,3), +(3,4), +(4,5), +(5,6), +(6,6), +(7,9), +(7,7), +(7,8); + +-- ============================================================ +-- Document Classifications +-- ============================================================ + +INSERT INTO "DocumentClassification" ("Index","ClassId","ClassificationSystem","ClassName_en","ClassName_de") VALUES +(0,'CLS-001','IEC-61360','Electrical Components','Elektrische Bauteile'), +(1,'CLS-002','ISO-13584','Hydraulic Pumps','Hydraulikpumpen'), +(0,'CLS-003','ECLASS-13.0','Industrial Sensors','Industrielle Sensoren'), +(0,'CLS-004','UNSPSC','Fasteners','Befestigungselemente'), +(0,'CLS-005','ISO-81346','Bearings','Lager'), +(0,'CLS-006','IEC-61131','PLC Controllers','PLC-Steuerungen'), +(1,'CLS-007','ECLASS-13.0','Safety Equipment','Sicherheitsausrüstung'), +(2,'CLS-008','GS1','Packaging Materials','Verpackungsmaterialien'), +(0,'CLS-009','ISO-13584','Cooling Systems','Kühlsysteme'); + +-- ============================================================ +-- Document-Classification Relationships +-- ============================================================ + +INSERT INTO "DocumentDocumentClassification" ("DocumentId","DocumentClassificationId") VALUES +(1,1), +(1,2), +(2,3), +(3,4), +(4,5), +(5,6), +(6,6), +(7,9), +(7,7), +(7,8); + +-- ============================================================ +-- Document Versions +-- ============================================================ + +INSERT INTO "DocumentVersion" ( + "Index","en","DigitalFile","Version","StatusSetDate","StatusValue","OrganizationShortName", + "OrganizationOfficialName","Title_en","Title_de","Subtitle_en","Subtitle_de","Description_en","Description_de", + "KeyWords_en","KeyWords_de","PreviewFile" +) VALUES +(0,'en','https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf','1','2023-01-01','Released','M&M','M&M Germany', + 'User Guide – DSLR Camera Model X100','Benutzerhandbuch – DSLR-Kamera Modell X100','Complete Instructions for Professional Photography','Vollständige Anleitung für professionelle Fotografie', + 'Detailed instructions for operating the X100 DSLR camera, including setup and troubleshooting.','Detaillierte Anweisungen zur Bedienung der DSLR-Kamera X100, einschließlich Einrichtung und Fehlerbehebung', + 'DSLR, Camera, Photography, User Guide, Setup','DSLR, Kamera, Fotografie, Benutzerhandbuch, Einrichtung','https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg'), + +(1,'en','https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf','1.1','2024-05-05','InReview','M&M','M&M India', + 'Technical Specification – Mirrorless Camera Z-Series','Technische Spezifikation – Systemkamera der Z-Serie','Detailed Specs for Advanced Imaging','Detaillierte Spezifikationen für fortschrittliche Bildgebung', + 'Comprehensive technical details of the Z-Series mirrorless camera, covering sensor and performance.','Umfassende technische Details der spiegellosen Kamera Z-Serie, einschließlich Sensor und Leistung.','Mirrorless, Camera, Specs, Imaging, Performance','Spiegellos, Kamera, Spezifikationen, Bildgebung, Leistung','https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg'), + +(0,'en','https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf','2.1','2026-01-01','Released','M&M','M&M China', + 'Maintenance Manual – Professional Camera Lens 50mm','Wartungshandbuch – Professionelles Kameraobjektiv 50 mm','Care and Cleaning Procedures','Pflege und Reinigungsverfahren', + 'Guidelines for cleaning and maintaining the 50mm professional lens for optimal performance.','Richtlinien zur Reinigung und Wartung des professionellen 50-mm-Objektivs für optimale Leistung.','Lens, Maintenance, Cleaning, Professional, Care','Objektiv, Wartung, Reinigung, Professionell, Pflege','https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg'), + +(0,'en','https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf','2.3','2025-10-10','InReview','M&M','M&M Germany', + 'Installation Guide – Wide-Angle Lens Kit','Installationsanleitung – Weitwinkel-Objektiv-Kit','Step-by-Step Setup Instructions','Schritt-für-Schritt-Installationsanleitung', + 'Step-by-step instructions for installing and configuring the wide-angle lens kit.','Schritt-für-Schritt-Anleitung zur Installation und Konfiguration des Weitwinkel-Objektivsets.','Wide-Angle, Lens, Installation, Setup, Kit','Weitwinkel, Objektiv, Installation, Einrichtung, Set','https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg'), + +(0,'en','https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf','0.9','2024-01-01','Released','M&M','M&M India', + 'Product Data Sheet – Telephoto Lens 200mm','Produktdatenblatt – Teleobjektiv 200 mm','Technical Data and Performance Metrics','Technische Daten und Leistungskennzahlen', + 'Technical data and compatibility details for the 200mm telephoto lens.','Technische Daten und Kompatibilitätsdetails für das 200-mm-Teleobjektiv.','Telephoto, Lens, Data Sheet, Specifications, Optics','Teleobjektiv, Objektiv, Datenblatt, Spezifikationen, Optik','https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg'), + +(0,'en','https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf','1.2','2024-03-03','InReview','M&M','M&M China', + 'Safety Instructions – Digital Camera Accessories','Sicherheitsanweisungen – Zubehör für Digitalkameras','Guidelines for Safe Usage','Richtlinien für sichere Verwendung', + 'Safety guidelines for handling batteries, chargers, and other camera accessories.','Sicherheitsrichtlinien für den Umgang mit Batterien, Ladegeräten und anderem Kamera-Zubehör.','Safety, Camera, Accessories, Guidelines, Handling','Sicherheit, Kamera, Zubehör, Richtlinien, Handhabung','https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg'), + +(1,'en','https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf','1.4','2023-01-01','Released','M&M','M&M Germany', + 'Perfume Catalog – Luxury Fragrance Collection 2025','Parfümkatalog – Luxusduftkollektion 2025','Explore Elegant Scents for Every Occasion','Entdecken Sie elegante Düfte für jeden Anlass', + 'A curated catalog showcasing premium perfumes with scent profiles and packaging details.','Ein kuratierter Katalog mit Premium-Parfums, Duftprofilen und Verpackungsdetails.','Perfume, Fragrance, Luxury, Catalog, Collection','Parfum, Duft, Luxus, Katalog, Kollektion','https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg'), + +(2,'en','https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf','2','2024-03-03','InReview','M&M','M&M India', + 'Quality Assurance Report – Eau de Parfum Series A','Qualitätssicherungsbericht – Eau de Parfum Serie A','Verified Standards and Testing Results','Geprüfte Standards und Testergebnisse', + 'Report detailing quality checks and compliance standards for Series A perfumes.','Bericht mit Qualitätsprüfungen und Konformitätsstandards für Parfums der Serie A.','Packaging, Perfume, Bottles, Caps, Compliance','Verpackung, Parfum, Flaschen, Verschlüsse, Konformität','https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg'), + +(0,'en','https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf','1','2022-02-02','Released','M&M','M&M Germany', + 'Packaging Standards – Perfume Bottles and Caps','Verpackungsstandards – Parfümflaschen und Verschlüsse','Design and Material Compliance Guidelines','Richtlinien für Design und Materialkonformität', + 'Design and material compliance guidelines for perfume packaging.','Richtlinien für Design und Materialkonformität bei Parfumverpackungen.','Perfume, Fragrance, Luxury, Catalog, Collection','Parfum, Duft, Luxus, Katalog, Kollektion','https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg'); + +-- ============================================================ +-- Document-Version Relationships +-- ============================================================ + +INSERT INTO "DocumentDocumentVersion" ("DocumentId","DocumentVersionId") VALUES +(1,1), +(1,2), +(2,3), +(3,4), +(4,5), +(5,6), +(6,6), +(7,9), +(7,7), +(7,8); diff --git a/example/postgres/init.sql b/example/postgres/init.sql new file mode 100644 index 0000000..e82ea3e --- /dev/null +++ b/example/postgres/init.sql @@ -0,0 +1,27 @@ +-- ============================================================ +-- DPP Plugin Database Initialization Script +-- ============================================================ +-- This is the main orchestration file that executes all database +-- initialization scripts in the correct hierarchical order. +-- +-- Execution Order: +-- 1. 01_core_asset_tables.sql.inc - Create core asset database schema +-- 2. 02_nameplate_carbonfootprint_technicaldata.sql.inc - Create Nameplate, Carbon Footprint, Technical Data tables and insert data +-- 3. 03_contactinformations.sql.inc - Create Contact information and relationships table and insert data +-- 4. 04_handoverdocumentation.sql.inc - Create Handover Documentation table and insert data +-- +-- ============================================================ + +\echo 'Executing: 01_core_asset_tables.sql.inc - Creating core asset database schema...' +\i /docker-entrypoint-initdb.d/01_core_asset_tables.sql.inc + +\echo 'Executing: 02_nameplate_carbonfootprint_technicaldata.sql.inc - Inserting data...' +\i /docker-entrypoint-initdb.d/02_nameplate_carbonfootprint_technicaldata.sql.inc + +\echo 'Executing: 03_contactinformations.sql.inc - Inserting contact information...' +\i /docker-entrypoint-initdb.d/03_contactinformations.sql.inc + +\echo 'Executing: 04_handoverdocumentation.sql.inc - Inserting document metadata...' +\i /docker-entrypoint-initdb.d/04_handoverdocumentation.sql.inc + +\echo 'Database initialization completed successfully!' diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRegistry/ShellDescriptorServiceTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRegistry/ShellDescriptorServiceTests.cs index 9848293..4de2a88 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRegistry/ShellDescriptorServiceTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRegistry/ShellDescriptorServiceTests.cs @@ -254,97 +254,92 @@ public async Task SyncShellDescriptorsAsync_ShouldHandle_EmptyListsGracefully() } [Fact] - public async Task SyncShellDescriptorsAsync_ShouldThrow_InternalDataProcessingException_WhenRegistryDescriptorHasNoId() + public async Task SyncShellDescriptorsAsync_WhenRegistryDescriptorHasNoId_ShouldLogAndReturn() { var metaData = new ShellDescriptorsMetaData { - PagingMetaData = new PagingMetaData { Cursor = "nextCursor" }, + PagingMetaData = new PagingMetaData(), ShellDescriptors = [new ShellDescriptorMetaData { Id = "valid" }] }; - _aasRegistryProvider.GetAllAsync(Arg.Any()).Returns([new ShellDescriptor { Id = "" }]); - var manifests = new List - { - new() - { - PluginName = "TestPlugin", - PluginUrl = new Uri("http://test-plugin"), - SupportedSemanticIds = new List(), - Capabilities = new Capabilities { HasShellDescriptor = true } - } - }; - _pluginManifestConflictHandler.Manifests.Returns(manifests); - _pluginDataHandler.GetDataForAllShellDescriptorsAsync(null, null, manifests, Arg.Any()).Returns(metaData); - await Assert.ThrowsAsync(() => _sut.SyncShellDescriptorsAsync(CancellationToken.None)); + _aasRegistryProvider + .GetAllAsync(Arg.Any()) + .Returns([new ShellDescriptor { Id = "" }]); + + _pluginDataHandler + .GetDataForAllShellDescriptorsAsync(null, null, Arg.Any>(), Arg.Any()) + .Returns(metaData); + + var exception = await Record.ExceptionAsync( + () => _sut.SyncShellDescriptorsAsync(CancellationToken.None)); + + Assert.Null(exception); + + await _aasRegistryProvider + .DidNotReceive() + .CreateAsync(Arg.Any(), Arg.Any()); } [Fact] - public async Task SyncShellDescriptorsAsync_WhenRegistryThrowsResourceNotFoundException_ThrowsShellDescriptorNotFoundException() + public async Task SyncShellDescriptorsAsync_WhenRegistryThrowsResourceNotFoundException_ShouldNotThrow() { - _aasRegistryProvider.GetAllAsync(Arg.Any()) - .Throws(new ResourceNotFoundException()); + _aasRegistryProvider + .GetAllAsync(Arg.Any()) + .Throws(new ResourceNotFoundException()); - var ex = await Assert.ThrowsAsync(() => _sut.SyncShellDescriptorsAsync(CancellationToken.None)); + var exception = await Record.ExceptionAsync( + () => _sut.SyncShellDescriptorsAsync(CancellationToken.None)); - Assert.NotNull(ex.InnerException); - Assert.IsType(ex.InnerException); + Assert.Null(exception); } [Fact] - public async Task SyncShellDescriptorsAsync_WhenRegistryThrowsResponseParsingException_ThrowsInternalDataProcessingException() + public async Task SyncShellDescriptorsAsync_WhenRegistryThrowsResponseParsingException_ShouldNotThrow() { - _aasRegistryProvider.GetAllAsync(Arg.Any()).Throws(new ResponseParsingException()); + _aasRegistryProvider + .GetAllAsync(Arg.Any()) + .Throws(new ResponseParsingException()); - var ex = await Assert.ThrowsAsync(() => _sut.SyncShellDescriptorsAsync(CancellationToken.None)); + var exception = await Record.ExceptionAsync( + () => _sut.SyncShellDescriptorsAsync(CancellationToken.None)); - Assert.NotNull(ex.InnerException); - Assert.IsType(ex.InnerException); + Assert.Null(exception); } [Fact] - public async Task SyncShellDescriptorsAsync_WhenRegistryThrowsRequestTimeoutException_ThrowsRegistryNotAvailableException() + public async Task SyncShellDescriptorsAsync_WhenRegistryThrowsRequestTimeoutException_ShouldNotThrow() { - _aasRegistryProvider.GetAllAsync(Arg.Any()).Throws(new RequestTimeoutException()); + _aasRegistryProvider + .GetAllAsync(Arg.Any()) + .Throws(new RequestTimeoutException()); - var ex = await Assert.ThrowsAsync(() => _sut.SyncShellDescriptorsAsync(CancellationToken.None)); + var exception = await Record.ExceptionAsync( + () => _sut.SyncShellDescriptorsAsync(CancellationToken.None)); - Assert.NotNull(ex.InnerException); - Assert.IsType(ex.InnerException); + Assert.Null(exception); } [Fact] - public async Task SyncShellDescriptorsAsync_ShouldThrow_InternalDataProcessingException_WhenPluginMetadataHasNoId() + public async Task SyncShellDescriptorsAsync_WhenPluginMetadataHasNoId_ShouldLogAndReturn() { var metaData = new ShellDescriptorsMetaData { - PagingMetaData = new PagingMetaData { Cursor = "nextCursor" }, + PagingMetaData = new PagingMetaData(), ShellDescriptors = [new ShellDescriptorMetaData { Id = "" }] }; - _aasRegistryProvider.GetAllAsync(Arg.Any()).Returns([new ShellDescriptor { Id = "1" }]); - var manifests = new List - { - new() - { - PluginName = "TestPlugin", - PluginUrl = new Uri("http://test-plugin"), - SupportedSemanticIds = new List(), - Capabilities = new Capabilities { HasShellDescriptor = true } - } - }; - _pluginManifestConflictHandler.Manifests.Returns(manifests); - _pluginDataHandler.GetDataForAllShellDescriptorsAsync(null, null, manifests, Arg.Any()).Returns(metaData); - await Assert.ThrowsAsync(() => _sut.SyncShellDescriptorsAsync(CancellationToken.None)); - } + _aasRegistryProvider + .GetAllAsync(Arg.Any()) + .Returns([new ShellDescriptor { Id = "1" }]); - [Fact] - public async Task SyncShellDescriptorsAsync_ShouldThrowException_WhenPluginFails() - { - _aasRegistryProvider.GetAllAsync(Arg.Any()).Returns([new ShellDescriptor { Id = "1" }]); + _pluginDataHandler + .GetDataForAllShellDescriptorsAsync(null, null, Arg.Any>(), Arg.Any()) + .Returns(metaData); - _pluginDataHandler.GetDataForAllShellDescriptorsAsync(null, null, Arg.Any>(), Arg.Any()).Throws(new InternalDataProcessingException()); + var exception = await Record.ExceptionAsync( + () => _sut.SyncShellDescriptorsAsync(CancellationToken.None)); - await Assert.ThrowsAsync(() => _sut.SyncShellDescriptorsAsync(CancellationToken.None)); + Assert.Null(exception); } [Fact] diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SemanticIdHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SemanticIdHandlerTests.cs index 457c508..fd96d9c 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SemanticIdHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SemanticIdHandlerTests.cs @@ -557,6 +557,31 @@ public void FillOutTemplate_SubmodelOfComplexData_ReturnsSubmodelWithValues() AssertContactInfo(complexData1, 2, "Test1 John Doe"); } + [Fact] + public void FillOutTemplate_SubmodelOfComplexData_ReturnsSubmodelWithValue() + { + var complexData = TestData.ComplexData; + var submodel = TestData.CreateSubmodelWithComplexData(); + submodel.SubmodelElements?.Add(complexData); + var values = TestData.CreateSubmodelWithInValidComplexDataTreeNode(); + + var submodelWithValues = (Submodel)_sut.FillOutTemplate(submodel, values); + + Equal(2, submodelWithValues.SubmodelElements!.Count); + Equal("ComplexData0", submodelWithValues.SubmodelElements[0].IdShort); + Equal("ComplexData1", submodelWithValues.SubmodelElements[1].IdShort); + var complexData0 = GetSubmodelElementCollection(submodelWithValues, 0); + var complexData1 = GetSubmodelElementCollection(submodelWithValues, 1); + Equal(3, complexData1.Value!.Count); + AssertMultiLanguageProperty(complexData0, "Test Example Manufacturer", "Test Beispiel Hersteller"); + AssertMultiLanguageProperty(complexData1, "Test1 Example Manufacturer", "Test1 Beispiel Hersteller"); + AssertModelType(complexData0, 1, "22.47"); + AssertModelType(complexData1, 1, "22.47"); + AssertContactList(complexData0, 2, "Test John Doe", "Test Example Model"); + AssertContactInfo(complexData0, 3, "Test John Doe"); + AssertContactInfo(complexData1, 2, "Test1 John Doe"); + } + [Fact] public void FillOutTemplate_ShouldNotChangeAnyThing_WhenReferenceElementHasNullValue() { diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateServiceTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateServiceTests.cs index 3a38608..f41ebbc 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateServiceTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateServiceTests.cs @@ -189,22 +189,64 @@ public async Task GetSubmodelTemplateAsync_WithListIndexPath_ReturnsSubmodelWith } [Fact] - public async Task GetSubmodelTemplateAsync_ThrowsNotFoundException_WhenListIndexIsOutOfRange() + public async Task GetSubmodelTemplateAsync_Supports_UrlEncoded_ListIndex() { var submodel = TestData.CreateSubmodelWithModel3DList(); - const string Path = "Model3D[5].ModelFile1"; + const string Path = "Model3D%5B0%5D.ModelDataFile"; + _mappingProvider.GetTemplateId(SubmodelId).Returns(TemplateId); _templateProvider.GetSubmodelTemplateAsync(TemplateId, Arg.Any()) .Returns(submodel); - await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync(SubmodelId, Path, CancellationToken.None)); + var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, Path, CancellationToken.None); + + Assert.NotNull(result); } [Fact] - public async Task GetSubmodelTemplateAsync_ThrowsNotFoundException_WhenListElementNotFound() + public async Task GetSubmodelTemplateAsync_Throws_When_ListIndex_IsNegative() { var submodel = TestData.CreateSubmodelWithModel3DList(); - const string Path = "NonExistentList[0].ModelFile1"; + const string Path = "Model3D[-1]"; + + _mappingProvider.GetTemplateId(SubmodelId).Returns(TemplateId); + _templateProvider.GetSubmodelTemplateAsync(TemplateId, Arg.Any()) + .Returns(submodel); + + await Assert.ThrowsAsync( + () => _sut.GetSubmodelTemplateAsync(SubmodelId, Path, CancellationToken.None)); + } + + [Fact] + public async Task GetSubmodelTemplateAsync_ReturnsSubmodel_WhenTypeValueListElementIsSubmodelCollection_AndListIndexExceedsAvailableElements() + { + var expectedSubmodel = TestData.CreateSubmodelWithModel3DList(); + var submodel = TestData.CreateSubmodelWithModel3DList(); + const string Path = "Model3D[5].ModelDataFile"; + _mappingProvider.GetTemplateId(SubmodelId).Returns(TemplateId); + _templateProvider.GetSubmodelTemplateAsync(TemplateId, Arg.Any()) + .Returns(submodel); + + var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, Path, CancellationToken.None); + + Assert.Equal(GetSemanticId(expectedSubmodel), GetSemanticId(result)); + + var list = result.SubmodelElements?.FirstOrDefault() as SubmodelElementList; + Assert.NotNull(list); + Assert.Single(list.Value!); + var collection = list.Value![0] as SubmodelElementCollection; + Assert.Single(collection!.Value!); + var file = collection!.Value!.FirstOrDefault() as File; + Assert.NotNull(file); + Assert.Equal("ModelDataFile", file.IdShort); + Assert.Equal("https://localhost/ModelDataFile.glb", file.Value); + } + + [Fact] + public async Task GetSubmodelTemplateAsync_ThrowsInternalDataProcessingException_WhenTypeValueListElementIsSubmodelProperty_AndListIndexExceedsAvailableElements() + { + var submodel = TestData.CreateSubmodelWithPropertyInsideList(); + const string Path = "listProperty[2]"; _mappingProvider.GetTemplateId(SubmodelId).Returns(TemplateId); _templateProvider.GetSubmodelTemplateAsync(TemplateId, Arg.Any()) .Returns(submodel); @@ -280,5 +322,4 @@ public async Task GetSubmodelTemplateAsync_WithIdShortPath_ThrowsSubmodelElement await Assert.ThrowsAsync( () => _sut.GetSubmodelTemplateAsync(SubmodelId, "SomePath", CancellationToken.None)); } - } diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/TestData.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/TestData.cs index b3732d2..07c7fbf 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/TestData.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/TestData.cs @@ -632,6 +632,41 @@ public static Submodel CreateSubmodelWithoutExtraElementsNested() ); } + public static SubmodelElementList CreateElementListWithProperty() + { + return new SubmodelElementList( + idShort: "listProperty", + semanticId: new Reference( + ReferenceTypes.ExternalReference, + [ + new Key(KeyTypes.SubmodelElementList, + "http://example.com/idta/digital-nameplate/list-property") + ] + ), + typeValueListElement: AasSubmodelElements.Property, + value: [ + CreateContactName() + ] + ); + } + + public static Submodel CreateSubmodelWithPropertyInsideList() + { + return new Submodel( + id: "http://example.com/idta/digital-nameplate", + idShort: "DigitalNameplate", + semanticId: new Reference( + ReferenceTypes.ExternalReference, + [ + new Key(KeyTypes.Submodel, "http://example.com/idta/digital-nameplate/semantic-id") + ] + ), + submodelElements: [ + CreateElementListWithProperty() + ] + ); + } + public static Submodel CreateSubmodelWithoutExtraElements() { return new Submodel( @@ -720,6 +755,45 @@ public static Submodel CreateSubmodelWithModel3DList() ] ); + public static readonly SubmodelElementCollection InValidComplexData = new( + idShort: "ComplexData", + semanticId: new Reference( + ReferenceTypes.ExternalReference, + [ + new Key(KeyTypes.SubmodelElementList, "http://example.com/idta/digital-nameplate/complex-data") + ] + ), + qualifiers: + [ + new Qualifier( + type: "ExternalReference", + valueType: DataTypeDefXsd.String, + value: "OneToMany") + ], + value: [ + CreateManufacturerName(), + new Property( + idShort: "ModelType", + valueType: DataTypeDefXsd.String, + value: "", // left intentionally empty for FillOut tests + semanticId: new Reference( + ReferenceTypes.ExternalReference, + [ + new Key(KeyTypes.Property, "http://example.com/idta/digital-nameplate/model-type") + ] + ), + qualifiers: + [ + new Qualifier( + type: "ExternalReference", + valueType: DataTypeDefXsd.String, + value: "ZeroToOne") + ]), + CreateContactList(), + CreateContactInformation(), + ] +); + public static readonly SemanticTreeNode SubmodelTreeNode = CreateSubmodelTreeNode(); public static SemanticTreeNode CreateSubmodelTreeNode() @@ -778,6 +852,41 @@ public static SemanticTreeNode CreateSubmodelWithComplexDataTreeNode() return semanticTreeNode; } + public static SemanticTreeNode CreateSubmodelWithInValidComplexDataTreeNode() + { + var semanticTreeNode = new SemanticBranchNode("http://example.com/idta/digital-nameplate/semantic-id", Cardinality.Unknown); + + var complexDataBranchNode1 = new SemanticBranchNode("http://example.com/idta/digital-nameplate/complex-data", Cardinality.ZeroToMany); + + var complexDataBranchNode2 = new SemanticBranchNode("http://example.com/idta/digital-nameplate/complex-data", Cardinality.ZeroToMany); + + semanticTreeNode.AddChild(complexDataBranchNode1); + + semanticTreeNode.AddChild(complexDataBranchNode2); + + complexDataBranchNode1.AddChild(CreateManufacturerNameTreeNode()); + + complexDataBranchNode1.AddChild(CreateModelTypeTreeNode()); + + complexDataBranchNode1.AddChild(CreateContactListTreeNode()); + + complexDataBranchNode1.AddChild(CreateContactInformationTreeNode()); + + complexDataBranchNode2.AddChild(CreateManufacturerNameTreeNode("1")); + + complexDataBranchNode2.AddChild(CreateModelTypeTreeNode()); + + complexDataBranchNode2.AddChild(CreateContactListTreeNode("1")); + + complexDataBranchNode2.AddChild(CreateContactListTreeNode("2")); + + complexDataBranchNode2.AddChild(new SemanticLeafNode("http://example.com/idta/digital-nameplate/contact-list", $"Test InValid Contact List", DataType.String, Cardinality.One)); + + complexDataBranchNode2.AddChild(CreateContactInformationTreeNode("1")); + + return semanticTreeNode; + } + public static SemanticTreeNode CreateManufacturerNameTreeNode(string testObject = "") { var manufacturerName = new SemanticBranchNode("http://example.com/idta/digital-nameplate/manufacturer-name", Cardinality.One); diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/ProviderTestData.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/ProviderTestData.cs index 67ac836..714ac2b 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/ProviderTestData.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/ProviderTestData.cs @@ -89,7 +89,7 @@ public static class ProviderTestData "contentType": "image/jpeg", "path": "http://localhost/fileprovider/k.jpg" }, - "globalAssetId": "https://sew-eurodrive.de/shell/1" + "globalAssetId": "https://mm-software.de/shell/1" } """; @@ -118,9 +118,9 @@ public static class ProviderTestData "specificAssetIds": [] }, { - "globalAssetId": "https://wago.com/ids/assets/2206-1631/1000-859", + "globalAssetId": "https://mm-software.com/ids/assets/2206-1631/1000-859", "idShort": "2206-1631/1000-859", - "id": "https://wago.com/ids/aas/2206-1631/1000-859", + "id": "https://mm-software.com/ids/aas/2206-1631/1000-859", "specificAssetIds": [] } ] diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/TemplateProvider/Services/TemplateProviderTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/TemplateProvider/Services/TemplateProviderTests.cs index 54bdea1..bc8e684 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/TemplateProvider/Services/TemplateProviderTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/TemplateProvider/Services/TemplateProviderTests.cs @@ -269,7 +269,7 @@ public async Task GetAssetInformationTemplateAsync_ReturnsAssetInformation_WhenV var result = await _sut.GetAssetInformationTemplateAsync(TemplateId, CancellationToken.None); Assert.NotNull(result); - Assert.Equal("https://sew-eurodrive.de/shell/1", result.GlobalAssetId); + Assert.Equal("https://mm-software.de/shell/1", result.GlobalAssetId); } [Fact] diff --git a/source/AAS.TwinEngine.DataEngine.sln b/source/AAS.TwinEngine.DataEngine.sln index 72313cb..b56b843 100644 --- a/source/AAS.TwinEngine.DataEngine.sln +++ b/source/AAS.TwinEngine.DataEngine.sln @@ -9,6 +9,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAS.TwinEngine.DataEngine.U EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAS.TwinEngine.DataEngine.ModuleTests", "AAS.TwinEngine.DataEngine.ModuleTests\AAS.TwinEngine.DataEngine.ModuleTests.csproj", "{D16C8CF8-6A29-4A40-971A-9A070A1817A9}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAS.TwinEngine.Plugin.TestPlugin", "AAS.TwinEngine.Plugin.TestPlugin\AAS.TwinEngine.Plugin.TestPlugin.csproj", "{455B960D-57D0-4D9B-805C-E1DEA05B9311}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAS.TwinEngine.Plugin.TestPlugin.UnitTests", "AAS.TwinEngine.Plugin.TestPlugin.UnitTests\AAS.TwinEngine.Plugin.TestPlugin.UnitTests.csproj", "{573F3A2B-8CC3-40D8-B543-74BF5DF7D4FF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +31,14 @@ Global {D16C8CF8-6A29-4A40-971A-9A070A1817A9}.Debug|Any CPU.Build.0 = Debug|Any CPU {D16C8CF8-6A29-4A40-971A-9A070A1817A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {D16C8CF8-6A29-4A40-971A-9A070A1817A9}.Release|Any CPU.Build.0 = Release|Any CPU + {455B960D-57D0-4D9B-805C-E1DEA05B9311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {455B960D-57D0-4D9B-805C-E1DEA05B9311}.Debug|Any CPU.Build.0 = Debug|Any CPU + {455B960D-57D0-4D9B-805C-E1DEA05B9311}.Release|Any CPU.ActiveCfg = Release|Any CPU + {455B960D-57D0-4D9B-805C-E1DEA05B9311}.Release|Any CPU.Build.0 = Release|Any CPU + {573F3A2B-8CC3-40D8-B543-74BF5DF7D4FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {573F3A2B-8CC3-40D8-B543-74BF5DF7D4FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {573F3A2B-8CC3-40D8-B543-74BF5DF7D4FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {573F3A2B-8CC3-40D8-B543-74BF5DF7D4FF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs index 63e2529..4a13fec 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs @@ -72,26 +72,37 @@ public class ShellDescriptorService( throw new InvalidUserInputException(ex); } } + public async Task SyncShellDescriptorsAsync(CancellationToken cancellationToken) { try { - var existingDescriptors = await aasRegistryProvider.GetAllAsync(cancellationToken).ConfigureAwait(false) ?? throw new RegistryNotAvailableException(); + var existingDescriptors = await aasRegistryProvider.GetAllAsync(cancellationToken).ConfigureAwait(false); + if (existingDescriptors == null) + { + logger.LogError("AAS Registry returned null. Sync skipped."); + return; + } var pluginManifests = pluginManifestConflictHandler.Manifests; - var pluginMetadata = await pluginDataHandler.GetDataForAllShellDescriptorsAsync(null, null, pluginManifests, cancellationToken).ConfigureAwait(false) ?? throw new PluginNotAvailableException(); + var pluginMetadata = await pluginDataHandler.GetDataForAllShellDescriptorsAsync(null, null, pluginManifests, cancellationToken).ConfigureAwait(false); + if (pluginMetadata == null) + { + logger.LogError("Plugin metadata unavailable. Sync skipped."); + return; + } if (existingDescriptors.Any(d => string.IsNullOrWhiteSpace(d.Id))) { logger.LogError("One or more registry descriptors have missing IDs: {@Descriptors}", existingDescriptors); - throw new InternalDataProcessingException(); + return; } if (pluginMetadata.ShellDescriptors.Any(m => string.IsNullOrWhiteSpace(m.Id))) { logger.LogError("One or more plugin metadata entries have missing IDs: {@Metadata}", pluginMetadata); - throw new InternalDataProcessingException(); + return; } var existingDescriptorsMap = existingDescriptors.ToDictionary(d => d.Id!); @@ -100,26 +111,9 @@ public async Task SyncShellDescriptorsAsync(CancellationToken cancellationToken) await CreateOrUpdateShellDescriptorsAsync(existingDescriptorsMap!, pluginMetadata.ShellDescriptors, cancellationToken).ConfigureAwait(false); await DeleteMissingShellDescriptorsAsync(existingDescriptors, pluginMetadataMap!, cancellationToken).ConfigureAwait(false); } - catch (ResourceNotFoundException ex) - { - throw new ShellDescriptorNotFoundException(ex); - } - catch (ResponseParsingException ex) - { - throw new InternalDataProcessingException(ex); - } - catch (RequestTimeoutException ex) - { - throw new RegistryNotAvailableException(ex); - } - catch (PluginMetaDataInvalidRequestException ex) - { - throw new InvalidUserInputException(ex); - } catch (Exception ex) { logger.LogError(ex, "Unexpected error during ShellDescriptor synchronization."); - throw new InternalDataProcessingException(); } } @@ -144,22 +138,10 @@ private async Task CreateOrUpdateShellDescriptorsAsync( await aasRegistryProvider.CreateAsync(newDescriptor, cancellationToken).ConfigureAwait(false); } } - catch (ResourceNotFoundException ex) - { - throw new ShellDescriptorNotFoundException(ex); - } - catch (ResponseParsingException ex) - { - throw new InternalDataProcessingException(ex); - } - catch (RequestTimeoutException ex) - { - throw new RegistryNotAvailableException(ex); - } catch (Exception ex) { logger.LogError(ex, "Unhandled error while processing descriptor with ID '{Id}'", metadata.Id); - throw new InternalDataProcessingException(ex); + continue; } } } @@ -184,14 +166,10 @@ private async Task DeleteMissingShellDescriptorsAsync( { await aasRegistryProvider.DeleteByIdAsync(descriptorId!, cancellationToken).ConfigureAwait(false); } - catch (RequestTimeoutException ex) - { - throw new ShellDescriptorNotFoundException(ex); - } catch (Exception ex) { logger.LogError(ex, "Unexpected error while deleting descriptor with ID '{Id}'", descriptorId); - throw new InternalDataProcessingException(ex); + continue; } } } diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SemanticIdHandler.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SemanticIdHandler.cs index 646b923..cca443c 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SemanticIdHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SemanticIdHandler.cs @@ -526,6 +526,15 @@ private void FillOutSubmodelElementValue(List elements, Semant continue; } + if (!AreAllNodesOfSameType(semanticTreeNodes, out _)) + { + logger.LogWarning("Mixed node types found for element '{IdShort}' with SemanticId '{SemanticId}'. Expected all nodes to be either SemanticBranchNode or SemanticLeafNode. Removing element.", + element.IdShort, + ExtractSemanticId(element)); + _ = elements.Remove(element); + continue; + } + if (semanticTreeNodes.Count > 1 && element is not Property && element is not ReferenceElement) { _ = elements.Remove(element); @@ -548,6 +557,25 @@ private void FillOutSubmodelElementValue(List elements, Semant } } + private static bool AreAllNodesOfSameType(List nodes, out Type? nodeType) + { + if (nodes.Count == 0) + { + nodeType = null; + return true; + } + + var firstNodeType = nodes[0].GetType(); + nodeType = firstNodeType; + + if (firstNodeType != typeof(SemanticBranchNode) && firstNodeType != typeof(SemanticLeafNode)) + { + return false; + } + + return nodes.All(node => node.GetType() == firstNodeType); + } + private void HandleSingleSemanticTreeNode(ISubmodelElement element, SemanticTreeNode node) => FillOutTemplate(element, node); private void FillOutMultiLanguageProperty(MultiLanguageProperty mlp, SemanticTreeNode values) @@ -800,7 +828,7 @@ private static IEnumerable FindNodeBySemanticId(SemanticTreeNo /// e.g. "element[3]" -> matches Group1= "element", Group2 = "3" /// Pattern: ^(.+?)\[(\d+)\]$ /// - [GeneratedRegex(@"^(.+?)\[(\d+)\]$")] + [GeneratedRegex(@"^(.+?)(?:\[(\d+)\]|%5B(\d+)%5D)$")] private static partial Regex SubmodelElementListIndex(); /// @@ -821,19 +849,27 @@ private static IEnumerable FindNodeBySemanticId(SemanticTreeNo return submodelElements?.FirstOrDefault(e => e.IdShort == idShort); } - private static bool TryParseIdShortWithBracketIndex(string segment, out string idShortWithoutIndex, out int index) + private static bool TryParseIdShortWithBracketIndex(string idShort, out string idShortWithoutIndex, out int index) { - var match = SubmodelElementListIndex().Match(segment); - if (match.Success) + var match = SubmodelElementListIndex().Match(idShort); + if (!match.Success) { - idShortWithoutIndex = match.Groups[1].Value; - index = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture); - return true; + idShortWithoutIndex = string.Empty; + index = -1; + return false; + } + + idShortWithoutIndex = match.Groups[1].Value; + var indexGroup = match.Groups[2].Success ? match.Groups[2] : match.Groups[3]; + if (!indexGroup.Success) + { + idShortWithoutIndex = string.Empty; + index = -1; + return false; } - idShortWithoutIndex = string.Empty; - index = -1; - return false; + index = int.Parse(indexGroup.Value, CultureInfo.InvariantCulture); + return true; } private ISubmodelElement GetElementFromListByIndex(IEnumerable? elements, string idShortWithoutIndex, int index) diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs index ab72948..385d5ca 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs @@ -5,6 +5,7 @@ using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Base; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasEnvironment.Providers; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; using AasCore.Aas3_0; @@ -131,16 +132,24 @@ private static ISubmodelElement FindMatchingElement(IEnumerable elements, string idShort) @@ -157,7 +166,20 @@ private static SubmodelElementList GetListElementByIdShort(List= list.Value?.Count) + if (index < 0) + { + throw new InternalDataProcessingException(); + } + + if (list.TypeValueListElement is AasSubmodelElements.SubmodelElementCollection or AasSubmodelElements.SubmodelElementList && list.Value?.Count > 0) + { + if (GetCardinality(list.Value.FirstOrDefault()!) is Cardinality.OneToMany or Cardinality.ZeroToMany) + { + return list.Value.FirstOrDefault()!; + } + } + + if (index >= list.Value?.Count) { throw new InternalDataProcessingException(); } @@ -183,7 +205,7 @@ private static SubmodelElementList GetListElementByIdShort(List matches Group1= "element", Group2 = "3" /// Pattern: ^(.+?)\[(\d+)\]$ /// - [GeneratedRegex(@"^(.+?)\[(\d+)\]$")] + [GeneratedRegex(@"^(.+?)(?:\[(\d+)\]|%5B(\d+)%5D)$")] private static partial Regex SubmodelElementListIndex(); /// @@ -201,4 +223,17 @@ private static void ValidateSubmodelId(string submodelId) throw new InternalDataProcessingException(); } } + + private static Cardinality GetCardinality(ISubmodelElement element) + { + var qualifierValue = element.Qualifiers?.FirstOrDefault()?.Value; + if (qualifierValue is null) + { + return Cardinality.Unknown; + } + + return Enum.TryParse(qualifierValue, ignoreCase: true, out var result) + ? result + : Cardinality.Unknown; + } } diff --git a/source/AAS.TwinEngine.DataEngine/Dockerfile b/source/AAS.TwinEngine.DataEngine/Dockerfile index 5ff23cd..63b409d 100644 --- a/source/AAS.TwinEngine.DataEngine/Dockerfile +++ b/source/AAS.TwinEngine.DataEngine/Dockerfile @@ -1,31 +1,23 @@ -#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. - -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base -USER app -WORKDIR /app -EXPOSE 8080 - -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:aa05b91be697b83229cb000b90120f0783604ad74ed92a0b45cdf3d1a9c873de AS build +# Install CycloneDX SBOM Generator Package +RUN dotnet tool install --global CycloneDX --version 5.5.0 +ENV PATH="$PATH:/root/.dotnet/tools" +# Build application ARG BUILD_CONFIGURATION=Release -WORKDIR /src -COPY ["AAS.TwinEngine.DataEngine/AAS.TwinEngine.DataEngine.csproj", "AAS.TwinEngine.DataEngine/"] +WORKDIR /App +COPY ["AAS.TwinEngine.DataEngine/", "AAS.TwinEngine.DataEngine/"] RUN dotnet restore "AAS.TwinEngine.DataEngine/AAS.TwinEngine.DataEngine.csproj" -COPY AAS.TwinEngine.DataEngine/ AAS.TwinEngine.DataEngine/ -WORKDIR "/src/AAS.TwinEngine.DataEngine" - -RUN dotnet build "AAS.TwinEngine.DataEngine.csproj" -c "$BUILD_CONFIGURATION" -o /app/build +RUN dotnet publish "AAS.TwinEngine.DataEngine/AAS.TwinEngine.DataEngine.csproj" -c "$BUILD_CONFIGURATION" -o out +# Generate Application SBOM at sbom/bom.xml (omitting dev/test dependencies as they do not appear in final build) +RUN dotnet-CycloneDX "AAS.TwinEngine.DataEngine/AAS.TwinEngine.DataEngine.csproj" -o "sbom/" --exclude-dev --exclude-test-projects --set-nuget-purl --spec-version 1.6 --disable-package-restore -FROM build AS publish -ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "AAS.TwinEngine.DataEngine.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false - -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final +# Create an "app-sbom-artifact" image which can be use to extract the pure APP SBOM after completing the docker build (we want to keep the SBOM for the application separate from the pure linux image SBOM as this provides more details and simplifies vuln management) +FROM scratch AS app-sbom-artifact +COPY --from=build /App/sbom/bom.xml /sbom_application.cyclonedx.xml -RUN useradd -m appuser \ - && mkdir /app \ - && chown appuser:appuser /app - -USER appuser -WORKDIR /app -COPY --from=publish /app/publish . +# Create final image containing application +FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine@sha256:a0ce42fe86548363a9602c47fc3bd4cf9c35a2705c68cd98d7ce18ae8735b83c +USER app +WORKDIR /App +COPY --from=build /App/out . ENTRYPOINT ["dotnet", "AAS.TwinEngine.DataEngine.dll"] diff --git a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/PluginDataProvider/Services/MultiPluginDataHandler.cs b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/PluginDataProvider/Services/MultiPluginDataHandler.cs index 516f5bd..05ce5f5 100644 --- a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/PluginDataProvider/Services/MultiPluginDataHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/PluginDataProvider/Services/MultiPluginDataHandler.cs @@ -131,7 +131,7 @@ public SemanticTreeNode Merge(SemanticTreeNode globalTree, IList valueTrees) + private SemanticTreeNode MergeBranch(SemanticBranchNode branch, IList valueTrees) { var mergedBranch = new SemanticBranchNode(branch.SemanticId, branch.Cardinality); @@ -241,8 +241,14 @@ private static List MergeBranchNodes(SemanticBranchNode templa } } - private static List FindMatchingNodes(IEnumerable valueTrees, string semanticId) + private List FindMatchingNodes(IEnumerable valueTrees, string semanticId) { + if (semanticId.Contains(_submodelElementIndexContextPrefix, StringComparison.Ordinal)) + { + var indexPrefixIndex = semanticId.IndexOf(_submodelElementIndexContextPrefix, StringComparison.OrdinalIgnoreCase); + semanticId = semanticId[..indexPrefixIndex]; + } + var matches = new List(); foreach (var vt in valueTrees.OfType()) diff --git a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/PluginDataProvider/Services/PluginDataHandler.cs b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/PluginDataProvider/Services/PluginDataHandler.cs index 98623a3..e7d6306 100644 --- a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/PluginDataProvider/Services/PluginDataHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/PluginDataProvider/Services/PluginDataHandler.cs @@ -11,6 +11,7 @@ using AAS.TwinEngine.DataEngine.DomainModel.Plugin; using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; using AAS.TwinEngine.DataEngine.Infrastructure.Providers.PluginDataProvider.Helper; +using AAS.TwinEngine.DataEngine.Infrastructure.Shared; using Json.Schema; @@ -82,7 +83,7 @@ public async Task GetDataForAllShellDescriptorsAsync(i try { - var shellDescriptorData = JsonSerializer.Deserialize(responseContent); + var shellDescriptorData = JsonSerializer.Deserialize(responseContent, JsonSerializationOptions.DeserializationOption); if (shellDescriptorData == null) { logger.LogError("Failed to deserialize All ShellDescriptorData. Response content: {Content}", responseContent); diff --git a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/TemplateProvider/Services/ShellTemplateMappingProvider.cs b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/TemplateProvider/Services/ShellTemplateMappingProvider.cs index 1d59b88..30b7577 100644 --- a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/TemplateProvider/Services/ShellTemplateMappingProvider.cs +++ b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/TemplateProvider/Services/ShellTemplateMappingProvider.cs @@ -43,7 +43,7 @@ public string GetProductIdFromRule(string aasIdentifier) }) .Where(x => x.Parts is { Length: >= 1 } && x.Rule.Index > 0 && x.Parts.Length >= x.Rule.Index) .Select(x => x.Parts![x.Rule.Index - 1]) - .FirstOrDefault(); + .FirstOrDefault(extractedId => !string.Equals(extractedId, aasIdentifier, StringComparison.Ordinal)); if (!string.IsNullOrEmpty(productId)) { diff --git a/source/AAS.TwinEngine.DataEngine/Infrastructure/Shared/JsonSerializationOptions.cs b/source/AAS.TwinEngine.DataEngine/Infrastructure/Shared/JsonSerializationOptions.cs index b3e4a39..5b38e80 100644 --- a/source/AAS.TwinEngine.DataEngine/Infrastructure/Shared/JsonSerializationOptions.cs +++ b/source/AAS.TwinEngine.DataEngine/Infrastructure/Shared/JsonSerializationOptions.cs @@ -25,4 +25,9 @@ public static class JsonSerializationOptions DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) } }; + + public static readonly JsonSerializerOptions DeserializationOption = new() + { + PropertyNameCaseInsensitive = true + }; } diff --git a/source/AAS.TwinEngine.DataEngine/Program.cs b/source/AAS.TwinEngine.DataEngine/Program.cs index b603dc6..ff0b43a 100644 --- a/source/AAS.TwinEngine.DataEngine/Program.cs +++ b/source/AAS.TwinEngine.DataEngine/Program.cs @@ -1,6 +1,4 @@ -using System.Globalization; - -using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; using AAS.TwinEngine.DataEngine.Infrastructure.Monitoring; using AAS.TwinEngine.DataEngine.Infrastructure.Providers.PluginDataProvider.Services; using AAS.TwinEngine.DataEngine.ServiceConfiguration; diff --git a/source/AAS.TwinEngine.DataEngine/appsettings.development.json b/source/AAS.TwinEngine.DataEngine/appsettings.development.json index bbce44b..071ae0f 100644 --- a/source/AAS.TwinEngine.DataEngine/appsettings.development.json +++ b/source/AAS.TwinEngine.DataEngine/appsettings.development.json @@ -13,10 +13,6 @@ { "PluginName": "Plugin1", "PluginUrl": "http://localhost:8086" - }, - { - "PluginName": "Plugin2", - "PluginUrl": "http://localhost:8087" } ] }, @@ -56,6 +52,18 @@ { "templateId": "https://admin-shell.io/idta/SubmodelTemplate/ContactInformation/1/0", "pattern": [ "ContactInformation" ] + }, + { + "templateId": "https://admin-shell.io/ZVEI/TechnicalData/Submodel/1/2", + "pattern": [ "TechnicalData" ] + }, + { + "templateId": "https://admin-shell.io/idta/SubmodelTemplate/CarbonFootprint/1/0", + "pattern": [ "CarbonFootprint" ] + }, + { + "templateId": "https://admin-shell.io/idta/SubmodelTemplate/HandoverDocumentation/2/0", + "pattern": [ "HandoverDocumentation" ] } ], "ShellTemplateMappings": [ diff --git a/source/AAS.TwinEngine.DataEngine/appsettings.json b/source/AAS.TwinEngine.DataEngine/appsettings.json index bbce44b..3c8b7ac 100644 --- a/source/AAS.TwinEngine.DataEngine/appsettings.json +++ b/source/AAS.TwinEngine.DataEngine/appsettings.json @@ -11,12 +11,8 @@ "PluginConfig": { "Plugins": [ { - "PluginName": "Plugin1", - "PluginUrl": "http://localhost:8086" - }, - { - "PluginName": "Plugin2", - "PluginUrl": "http://localhost:8087" + "PluginName": "", + "PluginUrl": "" } ] }, @@ -41,35 +37,22 @@ }, "AasRegistryPreComputed": { "ShellDescriptorCron": "0 */3 * * * *", - "IsPreComputed": true + "IsPreComputed": false }, "TemplateMappingRules": { "SubmodelTemplateMappings": [ { - "templateId": "https://admin-shell.io/idta/SubmodelTemplate/Reliability/1/0", - "pattern": [ "Reliability" ] - }, - { - "templateId": "https://admin-shell.io/idta/SubmodelTemplate/DigitalNameplate/3/0", - "pattern": [ "Nameplate" ] - }, - { - "templateId": "https://admin-shell.io/idta/SubmodelTemplate/ContactInformation/1/0", - "pattern": [ "ContactInformation" ] + "templateId": "", + "pattern": [ "" ] } ], "ShellTemplateMappings": [ { - "templateId": "https://mm-software.com/aas/aasTemplate", + "templateId": "", "pattern": [ "" ] } ], "AasIdExtractionRules": [ - { - "Pattern": "Regex", - "Index": 3, - "Separator": ":" - }, { "Pattern": "Regex", "Index": 6, diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/AAS.TwinEngine.Plugin.TestPlugin.UnitTests.csproj b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/AAS.TwinEngine.Plugin.TestPlugin.UnitTests.csproj new file mode 100644 index 0000000..617925c --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/AAS.TwinEngine.Plugin.TestPlugin.UnitTests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + enable + + false + true + true + IDE1006, IDE0058 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Manifest/Handler/ManifestHandlerTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Manifest/Handler/ManifestHandlerTests.cs new file mode 100644 index 0000000..520ea07 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Manifest/Handler/ManifestHandlerTests.cs @@ -0,0 +1,42 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Responses; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Manifest; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Manifest; + +using Microsoft.Extensions.Logging; + +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.Manifest.Handler; + +public class ManifestHandlerTests +{ + private readonly ILogger _logger = Substitute.For>(); + private readonly IManifestService _manifestService = Substitute.For(); + private readonly ManifestHandler _sut; + + public ManifestHandlerTests() => _sut = new ManifestHandler(_logger, _manifestService); + + [Fact] + public async Task GetManifestData_ShouldReturnDto_WhenManifestIsAvailable() + { + var manifest = new ManifestData { Capabilities = new CapabilitiesData { HasAssetInformation = true, HasShellDescriptor = true }, SupportedSemanticIds = ["test"] }; + var expectedDto = new ManifestDto { Capabilities = new CapabilitiesDto { HasAssetInformation = true, HasShellDescriptor = true }, SupportedSemanticIds = ["test"] }; + _manifestService.GetManifestData(Arg.Any()) + .Returns(Task.FromResult(manifest)); + + var result = await _sut.GetManifestData(CancellationToken.None); + + Assert.Equal(expectedDto.ToString(), result.ToString()); + } + + [Fact] + public async Task GetManifestData_ShouldThrowException_WhenServiceThrows() + { + _manifestService.GetManifestData(Arg.Any()) + .Throws(new Exception("Service failure")); + + await Assert.ThrowsAsync(() => _sut.GetManifestData(CancellationToken.None)); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Manifest/ManifestControllerTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Manifest/ManifestControllerTests.cs new file mode 100644 index 0000000..c21027b --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Manifest/ManifestControllerTests.cs @@ -0,0 +1,30 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Responses; + +using Microsoft.AspNetCore.Mvc; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.Manifest; + +public class ManifestControllerTests +{ + private readonly IManifestHandler _manifestHandler = Substitute.For(); + private readonly ManifestController _sut; + + public ManifestControllerTests() => _sut = new ManifestController(_manifestHandler); + + [Fact] + public async Task RetrieveManifestDataAsync_ShouldReturnOk_WhenDataIsAvailable() + { + var expectedManifest = new ManifestDto { Capabilities = new CapabilitiesDto(), SupportedSemanticIds = ["abc"] }; + _manifestHandler.GetManifestData(Arg.Any()) + .Returns(Task.FromResult(expectedManifest)); + + var result = await _sut.RetrieveManifestDataAsync(CancellationToken.None); + + var okResult = Assert.IsType(result.Result); + Assert.Equal(expectedManifest, okResult.Value); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Manifest/MappingProfiles/ManifestMappingProfileTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Manifest/MappingProfiles/ManifestMappingProfileTests.cs new file mode 100644 index 0000000..81bcc8d --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Manifest/MappingProfiles/ManifestMappingProfileTests.cs @@ -0,0 +1,37 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.MappingProfiles; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Manifest; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.Manifest.MappingProfiles; + +public class ManifestMappingProfileTests +{ + [Fact] + public void ToDto_ShouldMapManifestDataToManifestDtoCorrectly() + { + var manifestData = new ManifestData + { + Capabilities = new CapabilitiesData + { + HasAssetInformation = true, + HasShellDescriptor = false + }, + SupportedSemanticIds = ["semantic1", "semantic2"] + }; + + var result = manifestData.ToDto(); + + Assert.NotNull(result); + Assert.NotNull(result.Capabilities); + Assert.Equal(manifestData.Capabilities.HasAssetInformation, result.Capabilities.HasAssetInformation); + Assert.Equal(manifestData.Capabilities.HasShellDescriptor, result.Capabilities.HasShellDescriptor); + Assert.Equal(manifestData.SupportedSemanticIds, result.SupportedSemanticIds); + } + + [Fact] + public void ToDto_ShouldThrowException_WhenManifestDataIsNull() + { + ManifestData? manifestData = null; + + Assert.Throws(() => manifestData!.ToDto()); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/Handler/MetaDataHandlerTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/Handler/MetaDataHandlerTests.cs new file mode 100644 index 0000000..da6ea4f --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/Handler/MetaDataHandlerTests.cs @@ -0,0 +1,101 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Requests; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.MetaData; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +using Microsoft.Extensions.Logging; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.MetaData.Handler; + +public class MetaDataHandlerTests +{ + private readonly ILogger _logger = Substitute.For>(); + private readonly IMetaDataService _shellDescriptorService = Substitute.For(); + private readonly MetaDataHandler _sut; + + public MetaDataHandlerTests() => _sut = new MetaDataHandler(_logger, _shellDescriptorService); + + [Fact] + public async Task GetShellDescriptors_ReturnsShellDescriptorsDto_WhenDescriptorsExist() + { + var request = new GetShellDescriptorsRequest(10, "cursor123"); + var shellDescriptorsData = new ShellDescriptorsData + { + PagingMetaData = new PagingMetaData() { Cursor = "nextCursor" }, + Result = new List() + { + new() { Id = "desc1" }, + new() { Id = "desc2" } + } + }; + _shellDescriptorService.GetShellDescriptorsAsync(request.Limit, request.Cursor, Arg.Any()) + .Returns(shellDescriptorsData); + + var result = await _sut.GetShellDescriptors(request, CancellationToken.None); + + Assert.NotNull(result); + Assert.Equal(2, result.Result!.Count); + Assert.Equal("desc1", result.Result![0].Id); + Assert.Equal("desc2", result.Result![1].Id); + Assert.Equal("nextCursor", result.PagingMetaData!.Cursor); + } + + [Fact] + public async Task GetShellDescriptors_ThrowBadRequest_WhenLimitIsZero() + { + var request = new GetShellDescriptorsRequest(0, "cursor123"); + + var record = await Assert.ThrowsAsync(() => + _sut.GetShellDescriptors(request, CancellationToken.None)); + Assert.Equal(ExceptionMessages.InvalidRequestedLimit, record.Message); + } + + [Fact] + public async Task GetShellDescriptor_ReturnsShellDescriptor_WhenExists() + { + var request = new GetShellDescriptorRequest("test"); + var shellMetaData = new ShellDescriptorData { Id = "test" }; + _shellDescriptorService.GetShellDescriptorAsync("test", Arg.Any()).Returns(shellMetaData); + + var result = await _sut.GetShellDescriptor(request, CancellationToken.None); + + Assert.Equal(result.Id, shellMetaData.Id); + } + + [Fact] + public async Task GetShellDescriptor_ThrowsNotFoundException_WhenShellDoesNotExist() + { + var request = new GetShellDescriptorRequest("test"); + _shellDescriptorService.GetShellDescriptorAsync("test", Arg.Any())!.Returns((ShellDescriptorData)null!); + + await Assert.ThrowsAsync(() => _sut.GetShellDescriptor(request, CancellationToken.None)); + } + + [Fact] + public async Task GetAsset_ReturnsAsset_WhenExists() + { + var request = new GetAssetRequest("test-shell"); + var assetMetaData = new AssetData { GlobalAssetId = "test-shell" }; + _shellDescriptorService.GetAssetAsync("test-shell", Arg.Any()) + .Returns(assetMetaData); + + var result = await _sut.GetAsset(request, CancellationToken.None); + + Assert.Equal(assetMetaData.GlobalAssetId, result.GlobalAssetId); + } + + [Fact] + public async Task GetAsset_ThrowsNotFoundException_WhenAssetDoesNotExist() + { + var request = new GetAssetRequest("test-shell"); + _shellDescriptorService.GetAssetAsync("test-shell", Arg.Any()) + .Returns((AssetData)null!); + + await Assert.ThrowsAsync(() => + _sut.GetAsset(request, CancellationToken.None)); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MappingProfiles/AssetMappingProfileTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MappingProfiles/AssetMappingProfileTests.cs new file mode 100644 index 0000000..030b175 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MappingProfiles/AssetMappingProfileTests.cs @@ -0,0 +1,69 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.MappingProfiles; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.MetaData.MappingProfiles; + +public class AssetMappingProfileTests +{ + [Fact] + public void ToDto_MapsAllFieldsCorrectly_WhenThumbnailIsPresent() + { + var assetData = new AssetData + { + GlobalAssetId = "asset-001", + SpecificAssetIds = [new SpecificAssetIdsData { Name = "SerialNumber", Value = "SN001" }], + DefaultThumbnail = new DefaultThumbnailData + { + ContentType = "image/png", + Path = "/images/thumb.png" + } + }; + + var result = assetData.ToDto(); + + Assert.NotNull(result); + Assert.Equal("asset-001", result.GlobalAssetId); + Assert.Single(result.SpecificAssetIds!); + Assert.Equal("SerialNumber", result.SpecificAssetIds![0].Name); + Assert.Equal("SN001", result.SpecificAssetIds[0].Value); + Assert.NotNull(result.DefaultThumbnail); + Assert.Equal("image/png", result.DefaultThumbnail.ContentType); + Assert.Equal("/images/thumb.png", result.DefaultThumbnail.Path); + } + + [Fact] + public void ToDto_SetsDefaultThumbnailToNull_WhenThumbnailIsMissing() + { + var assetData = new AssetData + { + GlobalAssetId = "asset-002", + SpecificAssetIds = [], + DefaultThumbnail = null + }; + + var result = assetData.ToDto(); + + Assert.NotNull(result); + Assert.Equal("asset-002", result.GlobalAssetId); + Assert.Empty(result.SpecificAssetIds!); + Assert.Null(result.DefaultThumbnail); + } + + [Fact] + public void ToDto_HandlesNullSpecificAssetIds() + { + var assetData = new AssetData + { + GlobalAssetId = "asset-003", + SpecificAssetIds = null, + DefaultThumbnail = null + }; + + var result = assetData.ToDto(); + + Assert.NotNull(result); + Assert.Equal("asset-003", result.GlobalAssetId); + Assert.Null(result.DefaultThumbnail); + Assert.Null(result.SpecificAssetIds); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MappingProfiles/ShellDescriptorProfileTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MappingProfiles/ShellDescriptorProfileTests.cs new file mode 100644 index 0000000..700a250 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MappingProfiles/ShellDescriptorProfileTests.cs @@ -0,0 +1,73 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.MappingProfiles; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.MetaData.MappingProfiles; + +public class ShellDescriptorProfileTests +{ + [Fact] + public void ToDto_MapsAllFieldsCorrectly() + { + var shellDescriptorData = new ShellDescriptorData + { + Id = "shell-001", + GlobalAssetId = "asset-001", + IdShort = "Shell001", + SpecificAssetIds = + [ + new SpecificAssetIdsData { Name = "SerialNumber", Value = "SN001" }, + new SpecificAssetIdsData { Name = "PartNumber", Value = "PN001" } + ] + }; + + var result = shellDescriptorData.ToDto(); + + Assert.NotNull(result); + Assert.Equal("shell-001", result.Id); + Assert.Equal("asset-001", result.GlobalAssetId); + Assert.Equal("Shell001", result.IdShort); + Assert.Equal(2, result.SpecificAssetIds!.Count); + Assert.Equal("SerialNumber", result.SpecificAssetIds[0].Name); + Assert.Equal("SN001", result.SpecificAssetIds[0].Value); + } + + [Fact] + public void ToDto_HandlesNullSpecificAssetIds() + { + var shellDescriptorData = new ShellDescriptorData + { + Id = "shell-002", + GlobalAssetId = "asset-002", + IdShort = "Shell002", + SpecificAssetIds = null + }; + + var result = shellDescriptorData.ToDto(); + + Assert.NotNull(result); + Assert.Equal("shell-002", result.Id); + Assert.Equal("asset-002", result.GlobalAssetId); + Assert.Equal("Shell002", result.IdShort); + Assert.Null(result.SpecificAssetIds); + } + + [Fact] + public void ToDto_HandlesEmptySpecificAssetIds() + { + var shellDescriptorData = new ShellDescriptorData + { + Id = "shell-003", + GlobalAssetId = "asset-003", + IdShort = "Shell003", + SpecificAssetIds = [] + }; + + var result = shellDescriptorData.ToDto(); + + Assert.NotNull(result); + Assert.Equal("shell-003", result.Id); + Assert.Equal("asset-003", result.GlobalAssetId); + Assert.Equal("Shell003", result.IdShort); + Assert.Empty(result.SpecificAssetIds!); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MappingProfiles/ShellDescriptorsProfileTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MappingProfiles/ShellDescriptorsProfileTests.cs new file mode 100644 index 0000000..b6db442 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MappingProfiles/ShellDescriptorsProfileTests.cs @@ -0,0 +1,56 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.MappingProfiles; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.MetaData.MappingProfiles; + +public class ShellDescriptorsProfileTests +{ + [Fact] + public void ToDto_MapsAllFieldsCorrectly() + { + var shellDescriptorsData = new ShellDescriptorsData + { + PagingMetaData = new PagingMetaData { Cursor = "cursor-123" }, + Result = new List + { + new() + { + Id = "shell-001", + GlobalAssetId = "asset-001", + IdShort = "Shell001", + SpecificAssetIds = [new SpecificAssetIdsData { Name = "SerialNumber", Value = "SN001" }] + } + } + }; + + var result = shellDescriptorsData.ToDto(); + + Assert.NotNull(result); + Assert.NotNull(result.PagingMetaData); + Assert.Equal("cursor-123", result.PagingMetaData.Cursor); + Assert.Single(result.Result!); + Assert.Equal("shell-001", result.Result![0].Id); + Assert.Equal("asset-001", result.Result[0].GlobalAssetId); + Assert.Equal("Shell001", result.Result[0].IdShort); + Assert.Single(result.Result[0]!.SpecificAssetIds!); + Assert.Equal("SerialNumber", result.Result[0].SpecificAssetIds?[0]!.Name); + Assert.Equal("SN001", result.Result[0].SpecificAssetIds?[0].Value); + } + + [Fact] + public void ToDto_HandlesNullResultList() + { + var shellDescriptorsData = new ShellDescriptorsData + { + PagingMetaData = new PagingMetaData { Cursor = "cursor-456" }, + Result = null + }; + + var result = shellDescriptorsData.ToDto(); + + Assert.NotNull(result); + Assert.NotNull(result.PagingMetaData); + Assert.Equal("cursor-456", result.PagingMetaData.Cursor); + Assert.Null(result.Result); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MetaDataControllerTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MetaDataControllerTests.cs new file mode 100644 index 0000000..08e1ee9 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/MetaData/MetaDataControllerTests.cs @@ -0,0 +1,148 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData; +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Requests; +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Responses; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; + +using Microsoft.AspNetCore.Mvc; + +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.MetaData; + +public class MetaDataControllerTests +{ + private readonly IMetaDataHandler _handler = Substitute.For(); + private readonly MetaDataController _sut; + private const string AasIdentifier = "dGVzdA=="; + + public MetaDataControllerTests() => _sut = new MetaDataController(_handler); + + [Fact] + public async Task GetShellDescriptorsAsync_ReturnsOk_WithShellList() + { + var request = new GetShellDescriptorsRequest(null, null); + var expectedShells = new ShellDescriptorsDto(); + _handler.GetShellDescriptors(request, Arg.Any()).Returns(expectedShells); + + var result = await _sut.GetShellDescriptorsAsync(null, null, CancellationToken.None); + + var okResult = Assert.IsType(result.Result); + var actualShells = Assert.IsType(okResult.Value); + Assert.Equal(expectedShells, actualShells); + } + + [Fact] + public async Task GetShellDescriptorsAsync_ThrowsNotFoundException_Returns404() + { + var request = new GetShellDescriptorsRequest(null, null); + _handler.GetShellDescriptors(request, Arg.Any()) + .Throws(new NotFoundException("Shell not found")); + + await Assert.ThrowsAsync(() => _sut.GetShellDescriptorsAsync(null, null, CancellationToken.None)); + } + + [Fact] + public async Task GetShellDescriptorsAsync_ThrowsBadRequestException_Returns400() + { + var request = new GetShellDescriptorsRequest(null, null); + _handler.GetShellDescriptors(request, Arg.Any()) + .Throws(new BadRequestException("Invalid request")); + + await Assert.ThrowsAsync(() => _sut.GetShellDescriptorsAsync(null, null, CancellationToken.None)); + } + + [Fact] + public async Task GetShellDescriptorsAsync_ThrowsException_Returns500() + { + var request = new GetShellDescriptorsRequest(null, null); + _handler.GetShellDescriptors(request, Arg.Any()) + .Throws(new Exception("Unexpected error")); + + await Assert.ThrowsAsync(() => _sut.GetShellDescriptorsAsync(null, null, CancellationToken.None)); + } + + [Fact] + public async Task GetShellDescriptorAsync_ReturnsOk_WithShell() + { + var expectedShell = new ShellDescriptorDto(); + _handler.GetShellDescriptor(Arg.Any(), Arg.Any()) + .Returns(expectedShell); + + var result = await _sut.GetShellDescriptorAsync(AasIdentifier, CancellationToken.None); + + var okResult = Assert.IsType(result.Result); + var actualShell = Assert.IsType(okResult.Value); + Assert.Equal(expectedShell, actualShell); + } + + [Fact] + public async Task GetShellDescriptorAsync_ThrowsNotFoundException_Returns404() + { + _handler.GetShellDescriptor(Arg.Any(), Arg.Any()) + .Throws(new NotFoundException("Shell not found")); + + await Assert.ThrowsAsync(() => _sut.GetShellDescriptorAsync(AasIdentifier, CancellationToken.None)); + } + + [Fact] + public async Task GetShellDescriptorAsync_ThrowsBadRequestException_Returns400() + { + _handler.GetShellDescriptor(Arg.Any(), Arg.Any()) + .Throws(new BadRequestException("Invalid AAS identifier")); + + await Assert.ThrowsAsync(() => _sut.GetShellDescriptorAsync(AasIdentifier, CancellationToken.None)); + } + + [Fact] + public async Task GetShellDescriptorAsync_ThrowsException_Returns500() + { + _handler.GetShellDescriptor(Arg.Any(), Arg.Any()) + .Throws(new Exception("Unexpected error")); + + await Assert.ThrowsAsync(() => _sut.GetShellDescriptorAsync(AasIdentifier, CancellationToken.None)); + } + + [Fact] + public async Task GetAssetAsync_ReturnsOk_WithAssetInformation() + { + var expectedAssetInfo = new AssetDto(); + _handler.GetAsset(Arg.Any(), Arg.Any()) + .Returns(expectedAssetInfo); + + var result = await _sut.GetAssetAsync(AasIdentifier, CancellationToken.None); + + var okResult = Assert.IsType(result.Result); + var actualAssetInfo = Assert.IsType(okResult.Value); + Assert.Equal(expectedAssetInfo, actualAssetInfo); + } + + [Fact] + public async Task GetAssetAsync_ThrowsNotFoundException_Returns404() + { + _handler.GetAsset(Arg.Any(), Arg.Any()) + .Throws(new NotFoundException("Asset not found")); + + await Assert.ThrowsAsync(() => _sut.GetAssetAsync(AasIdentifier, CancellationToken.None)); + } + + [Fact] + public async Task GetAssetAsync_ThrowsBadRequestException_Returns400() + { + _handler.GetAsset(Arg.Any(), Arg.Any()) + .Throws(new BadRequestException("Invalid request")); + + await Assert.ThrowsAsync(() => _sut.GetAssetAsync(AasIdentifier, CancellationToken.None)); + } + + [Fact] + public async Task GetAssetAsync_ThrowsException_Returns500() + { + _handler.GetAsset(Arg.Any(), Arg.Any()) + .Throws(new Exception("Unexpected error")); + + await Assert.ThrowsAsync(() => + _sut.GetAssetAsync(AasIdentifier, CancellationToken.None)); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Handler/SubmodelHandlerTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Handler/SubmodelHandlerTests.cs new file mode 100644 index 0000000..cabd0f7 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Handler/SubmodelHandlerTests.cs @@ -0,0 +1,143 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Requests; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +using Json.Schema; + +using Microsoft.Extensions.Logging; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.Submodel.Handler; + +public class SubmodelHandlerTests +{ + private const string JsonSchemaString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""https://admin-shell.io/zvei/nameplate/1/0/ContactInformations"": { + ""type"": ""object"", + ""properties"": { + ""https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation"": { + ""anyOf"": [ + { ""$ref"": ""#/definitions/ContactInformation"" }, + { + ""type"": ""array"", + ""items"": { ""$ref"": ""#/definitions/ContactInformation"" } + } + ] + } + }, + ""required"": [ + ""https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation"" + ] + } + }, + ""definitions"": { + ""ContactInformation"": { + ""type"": ""object"", + ""properties"": { + ""0173-1#02-AAO204#003"": { ""type"": ""string"" }, + ""https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Language"": { ""type"": ""string"" } + }, + ""required"": [ + ""0173-1#02-AAO204#003"", + ""https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Language"" + ] + } + }}"; + + private const string JsonResponse = @"{ + ""https://admin-shell.io/zvei/nameplate/1/0/ContactInformations"": { + ""https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation"": { + ""0173-1#02-AAO204#003"": ""John Doe"", + ""https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Language"": ""en"" + } + } + }"; + + private readonly JsonSerializerOptions _options = new() + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + ReadCommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) + } + }; + + private readonly JsonSchema JsonSchemaRequest; + private readonly JsonObject _expectedResponse; + private readonly ILogger _logger = Substitute.For>(); + private readonly ISubmodelService _pluginService = Substitute.For(); + private readonly IJsonSchemaParser _jsonSchemaParser = Substitute.For(); + private readonly SubmodelHandler _sut; + private readonly GetSubmodelDataRequest _request; + private readonly SemanticBranchNode _semanticTree; + private readonly SemanticBranchNode _sematicTreeWithData; + private readonly ISemanticTreeHandler _semanticTreeHandler = Substitute.For(); + + public SubmodelHandlerTests() + { + _semanticTree = new SemanticBranchNode("https://admin-shell.io/zvei/nameplate/1/0/ContactInformations", DataType.Object); + var contactNode = new SemanticBranchNode("https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation", DataType.Object); + contactNode.AddChild(new SemanticLeafNode("0173-1#02-AAO204#003", DataType.String, "")); + contactNode.AddChild(new SemanticLeafNode( + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Language", DataType.String, "")); + _semanticTree.AddChild(contactNode); + + _sematicTreeWithData = new SemanticBranchNode("https://admin-shell.io/zvei/nameplate/1/0/ContactInformations", DataType.Object); + var contactNodeWithData = new SemanticBranchNode("https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation", DataType.Object); + contactNodeWithData.AddChild(new SemanticLeafNode("0173-1#02-AAO204#003", DataType.String, "John Doe")); + contactNodeWithData.AddChild(new SemanticLeafNode( + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Language", DataType.String, "en")); + _sematicTreeWithData.AddChild(contactNodeWithData); + + _expectedResponse = JsonNode.Parse(JsonResponse)!.AsObject(); + JsonSchemaRequest = JsonSerializer.Deserialize(JsonSchemaString, _options); + _sut = new SubmodelHandler(_logger, _pluginService, _jsonSchemaParser, _semanticTreeHandler); + _request = new GetSubmodelDataRequest("ContactInformation", JsonSchemaRequest); + } + + [Fact] + public async Task Handle_ReturnsJsonObject_WhenParserAndServiceSucceed() + { + _jsonSchemaParser.ParseJsonSchema(JsonSchemaRequest).Returns(_semanticTree); + _pluginService.GetValuesBySemanticIds(_semanticTree, "ContactInformation").Returns(_sematicTreeWithData); + _semanticTreeHandler.GetJson(_sematicTreeWithData, JsonSchemaRequest).Returns(_expectedResponse); + + var actual = await _sut.GetSubmodelData(_request, CancellationToken.None); + + Assert.Same(_expectedResponse, actual); + _jsonSchemaParser.Received(1).ParseJsonSchema(JsonSchemaRequest); + _pluginService.Received(1).GetValuesBySemanticIds(_semanticTree, "ContactInformation"); + _semanticTreeHandler.Received(1).GetJson(_sematicTreeWithData, JsonSchemaRequest); + } + + [Fact] + public async Task Handle_CallsServiceEvenWhenParserReturnsEmptyList() + { + var emptyBranch = new SemanticBranchNode("emptyRoot", DataType.Object); + _jsonSchemaParser.ParseJsonSchema(JsonSchemaRequest).Returns(emptyBranch); + var emptyResponse = new JsonObject(); + _pluginService.GetValuesBySemanticIds(emptyBranch, "ContactInformation").Returns(emptyBranch); + _semanticTreeHandler.GetJson(emptyBranch, JsonSchemaRequest).Returns(emptyResponse); + + var actual = await _sut.GetSubmodelData(_request, CancellationToken.None); + + Assert.Same(emptyResponse, actual); + _jsonSchemaParser.Received(1).ParseJsonSchema(JsonSchemaRequest); + _pluginService.Received(1).GetValuesBySemanticIds(emptyBranch, "ContactInformation"); + _semanticTreeHandler.Received(1).GetJson(emptyBranch, JsonSchemaRequest); + } +} + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Services/JsonSchemaParserTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Services/JsonSchemaParserTests.cs new file mode 100644 index 0000000..a0513cd --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Services/JsonSchemaParserTests.cs @@ -0,0 +1,389 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +using Json.Schema; + +using Microsoft.Extensions.Logging; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.Submodel.Services; + +public class JsonSchemaParserTests +{ + private readonly JsonSerializerOptions _options = new() + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + ReadCommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) + } + }; + + private readonly string _invalidJson = @"{""Invalid json"": {}}"; + + private const string ValidationFailSchemaString = @"{ ""type"" : ""null"" }"; + + private const string NoPropertiesSchemaString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"" + }"; + + private const string SimpleSchemaString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""foo"": { ""type"": ""string"" } + }}"; + + private const string NestedSchemaString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""parent"": { + ""type"": ""object"", + ""properties"": { + ""child"": { ""type"": ""number"" } + }}}}"; + + private const string ArraySchemaString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""list"": { + ""type"": ""array"", + ""properties"": { ""id"": { ""type"": ""integer"" } } + }}}"; + + private const string ArrayWithRefSchemaString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""items"": { + ""type"": ""array"", + ""$ref"": ""#/definitions/ItemDef"" + } + }, + ""definitions"": { + ""ItemDef"": { + ""type"": ""object"", + ""properties"": { ""val"": { ""type"": ""integer"" } } + } + } + }"; + + private const string AllDataTypesSchemaWithRefString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""root"" :{ + ""type"" : ""array"", + ""properties"" : { + ""stringField"": { ""type"": ""string"" }, + ""numberField"": { ""type"": ""number"" }, + ""integerField"": { ""type"": ""integer"" }, + ""booleanField"": { ""type"": ""boolean"" }, + ""arrayField"": { + ""$ref"" : ""#/definitions/itemField"" + }, + ""objectField"": { + ""type"": ""object"", + ""properties"": { + ""nestedProp"": { ""type"": ""string"" } + } + }, + ""nullField"": { ""type"": ""null"" } + } + } + }, + ""definitions"" : { + ""itemField"":{ + ""type"":""array"", + ""properties"": { + ""items"": { ""type"": ""string"" } + } + } + } + }"; + + private readonly ILogger _logger; + private readonly JsonSchemaParser _sut; + + public JsonSchemaParserTests() + { + _logger = Substitute.For>(); + _sut = new JsonSchemaParser(_logger); + } + + [Fact] + public void ParseJsonSchema_InvalidJson_ThrowsBadRequestException() + { + var InvalidJsonSchema = JsonSerializer.Deserialize(_invalidJson, _options); + + Assert.Throws(() => _sut.ParseJsonSchema(InvalidJsonSchema)); + } + + [Fact] + public void ParseJsonSchema_SchemaValidationFails_ThrowsBadRequestException() + { + var ValidationFailSchema = JsonSerializer.Deserialize(ValidationFailSchemaString, _options); + + Assert.Throws(() => _sut.ParseJsonSchema(ValidationFailSchema)); + } + + [Fact] + public void ParseJsonSchema_NoRootProperties_ThrowsBadRequestException() + { + var NoPropertiesSchema = JsonSerializer.Deserialize(NoPropertiesSchemaString, _options); + + Assert.Throws(() => _sut.ParseJsonSchema(NoPropertiesSchema)); + } + + [Fact] + public void ParseJsonSchema_SimpleSchema_ReturnsLeafNode() + { + var SimpleSchema = JsonSerializer.Deserialize(SimpleSchemaString, _options); + + var node = _sut.ParseJsonSchema(SimpleSchema); + + Assert.NotNull(node); + Assert.IsType(node); + var leaf = (SemanticLeafNode)node; + Assert.Equal("foo", leaf.SemanticId); + Assert.Equal(string.Empty, leaf.Value); + } + + [Fact] + public void ParseJsonSchema_NestedObject_ReturnsBranchNodeWithChild() + { + var NestedSchema = JsonSerializer.Deserialize(NestedSchemaString, _options); + + var node = _sut.ParseJsonSchema(NestedSchema); + + Assert.NotNull(node); + Assert.IsType(node); + var branch = (SemanticBranchNode)node; + Assert.Equal("parent", branch.SemanticId); + Assert.Single(branch.Children); + var child = branch.Children[0] as SemanticLeafNode; + Assert.NotNull(child); + Assert.Equal("child", child.SemanticId); + } + + [Fact] + public void ParseJsonSchema_ArrayOfObjects_ReturnsBranchNodeWithLeafChild() + { + var arraySchema = JsonSerializer.Deserialize(ArraySchemaString, _options); + + var node = _sut.ParseJsonSchema(arraySchema!); + + Assert.NotNull(node); + Assert.IsType(node); + var branch = (SemanticBranchNode)node; + Assert.Equal("list", branch.SemanticId); + Assert.Single(branch.Children); + var child = branch.Children[0] as SemanticLeafNode; + Assert.NotNull(child); + Assert.Equal("id", child.SemanticId); + } + + [Fact] + public void ParseJsonSchema_ArrayWithRef_ReturnsBranchNodeWithLeafChild() + { + var arrayWithRefSchema = JsonSerializer.Deserialize(ArrayWithRefSchemaString, _options); + + var node = _sut.ParseJsonSchema(arrayWithRefSchema!); + + Assert.NotNull(node); + Assert.IsType(node); + var branch = (SemanticBranchNode)node; + Assert.Equal("items", branch.SemanticId); + Assert.Single(branch.Children); + var child = branch.Children[0] as SemanticLeafNode; + Assert.NotNull(child); + Assert.Equal("val", child.SemanticId); + } + + [Fact] + public void ParseJsonSchema_AllDataTypeSchemaWithRef_ReturnsBranchNodeWithLeafChild() + { + var allDataTypesSchemaWithRef = JsonSerializer.Deserialize(AllDataTypesSchemaWithRefString, _options); + + var node = _sut.ParseJsonSchema(allDataTypesSchemaWithRef!); + + Assert.NotNull(node); + Assert.IsType(node); + var branch = (SemanticBranchNode)node; + Assert.Equal("root", branch.SemanticId); + Assert.Equal(DataType.Array, branch.DataType); + var child1 = branch.Children[0] as SemanticLeafNode; + Assert.Equal("stringField", child1!.SemanticId); + Assert.Equal(DataType.String, child1.DataType); + var child2 = branch.Children[1] as SemanticLeafNode; + Assert.Equal("numberField", child2!.SemanticId); + Assert.Equal(DataType.Number, child2.DataType); + var child3 = branch.Children[2] as SemanticLeafNode; + Assert.Equal("integerField", child3!.SemanticId); + Assert.Equal(DataType.Integer, child3.DataType); + var child4 = branch.Children[3] as SemanticLeafNode; + Assert.Equal("booleanField", child4!.SemanticId); + Assert.Equal(DataType.Boolean, child4.DataType); + var branch1 = branch.Children[4] as SemanticBranchNode; + Assert.Equal("arrayField", branch1?.SemanticId); + Assert.Equal(DataType.Array, branch1?.DataType); + var leaf1 = branch1!.Children[0] as SemanticLeafNode; + Assert.Equal("items", leaf1!.SemanticId); + Assert.Equal(DataType.String, leaf1.DataType); + } + + [Fact] + public void ParseJsonSchema_ReferenceNotFound_ReturnsLeafNode() + { + const string SchemaString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""mystery"": { ""$ref"": ""#/definitions/DoesNotExist"" } + } + }"; + var schema = JsonSerializer.Deserialize(SchemaString, _options); + + var node = _sut.ParseJsonSchema(schema!); + + Assert.NotNull(node); + Assert.IsType(node); + var leaf = (SemanticLeafNode)node; + Assert.Equal("mystery", leaf.SemanticId); + Assert.Equal(DataType.Unknown, leaf.DataType); + } + + [Fact] + public void ParseJsonSchema_ReferenceToObjectDefinition_ReturnsBranchNode() + { + const string SchemaString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""person"": { ""$ref"": ""#/definitions/Person"" } + }, + ""definitions"": { + ""Person"": { + ""type"": ""object"", + ""properties"": { + ""name"": { ""type"": ""string"" }, + ""age"": { ""type"": ""integer"" } + } + } + } + }"; + var schema = JsonSerializer.Deserialize(SchemaString, _options); + + var node = _sut.ParseJsonSchema(schema); + + Assert.NotNull(node); + Assert.IsType(node); + var branch = (SemanticBranchNode)node; + Assert.Equal("person", branch.SemanticId); + Assert.Equal(DataType.Object, branch.DataType); + Assert.Collection(branch.Children, + child => + { + var leaf = Assert.IsType(child); + Assert.Equal("name", leaf.SemanticId); + Assert.Equal(DataType.String, leaf.DataType); + }, + child => + { + var leaf = Assert.IsType(child); + Assert.Equal("age", leaf.SemanticId); + Assert.Equal(DataType.Integer, leaf.DataType); + }); + } + + [Fact] + public void ParseJsonSchema_ReferenceToArrayDefinition_ReturnsBranchNode() + { + const string SchemaString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""primes"": { ""$ref"": ""#/definitions/PrimeList"" } + }, + ""definitions"": { + ""PrimeList"": { + ""type"": ""array"" + } + } + }"; + var schema = JsonSerializer.Deserialize(SchemaString, _options); + + var node = _sut.ParseJsonSchema(schema); + + Assert.NotNull(node); + Assert.IsType(node); + var branch = (SemanticBranchNode)node; + Assert.Equal("primes", branch.SemanticId); + Assert.Equal(DataType.Array, branch.DataType); + Assert.Empty(branch.Children); + } + + [Fact] + public void ParseJsonSchema_ReferenceToLeafNodeDefinition_ReturnsLeafNode() + { + const string SchemaString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""person"": { ""$ref"": ""#/definitions/Person"" } + }, + ""definitions"": { + ""Person"": { + ""type"": ""string"" + } + } + }"; + var schema = JsonSerializer.Deserialize(SchemaString, _options); + + var node = _sut.ParseJsonSchema(schema); + + Assert.NotNull(node); + Assert.IsType(node); + var branch = (SemanticLeafNode)node; + Assert.Equal("person", branch.SemanticId); + Assert.Equal(DataType.String, branch.DataType); + } + + [Fact] + public void ParseJsonSchema_InlineObjectWithNoProperties_CoversProcessObjectFallback() + { + const string SchemaString = @"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""outer"": { + ""type"": ""object"", + ""properties"": { + ""inner"": { ""type"": ""object"" } + } + } + } + }"; + var schema = JsonSerializer.Deserialize(SchemaString, _options); + + var root = _sut.ParseJsonSchema(schema); + + var outerBranch = Assert.IsType(root); + Assert.Equal("outer", outerBranch.SemanticId); + Assert.Single(outerBranch.Children); + var innerBranch = Assert.IsType(outerBranch.Children[0]); + Assert.Equal("inner", innerBranch.SemanticId); + Assert.Empty(innerBranch.Children); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Services/JsonSchemaValidatorTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Services/JsonSchemaValidatorTests.cs new file mode 100644 index 0000000..808a410 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Services/JsonSchemaValidatorTests.cs @@ -0,0 +1,180 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel.Config; + +using Json.Schema; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.Submodel.Services; + +public class JsonSchemaValidatorTests +{ + private readonly JsonSchemaValidator _sut; + + public static IEnumerable InvalidPrimitives => [ + [SchemaValueType.String, "name", 123], + [SchemaValueType.Integer, "count", 12.34], + [SchemaValueType.Number, "price", "19.99a"], + [SchemaValueType.Boolean, "flag", "flase"], + [SchemaValueType.Number, "age", "8o5"], + [SchemaValueType.Number, "age", "-10n5"], + [SchemaValueType.Integer, "name", "10o"], + [SchemaValueType.Boolean, "flag", "\"true\""] + ]; + + public JsonSchemaValidatorTests() + { + var semantics = Substitute.For>(); + semantics.Value.Returns(new Semantics + { + IndexContextPrefix = "_aastwinengine_" + }); + var logger = Substitute.For>(); + _sut = new JsonSchemaValidator(semantics, logger); + } + + [Fact] + public void ValidateResponseContent_EmptyResponse_ThrowsBadRequest() + { + var schema = new JsonSchemaBuilder().Type(SchemaValueType.Object).Build(); + + Assert.Throws(() => _sut.ValidateResponseContent("", schema)); + } + + [Fact] + public void ValidateResponseContent_ValidateJsonSchemaRemovePrefix_DoesNotThrow() + { + var schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(new Dictionary + { + ["ContactInformation_aastwinengine_00"] = new JsonSchemaBuilder().Type(SchemaValueType.Object).Build() + }) + .Required("ContactInformation_aastwinengine_00") + .Build(); + + const string Json = "{\"ContactInformation\": {}}"; + + _sut.ValidateResponseContent(Json, schema); + } + + [Fact] + public void ValidateResponseContent_ValidJsonAndSchema_DoesNotThrow() + { + var schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(new Dictionary + { + ["name"] = new JsonSchemaBuilder().Type(SchemaValueType.String).Build() + }) + .Required("name") + .Build(); + + const string Json = "{\"name\": \"Test\"}"; + + _sut.ValidateResponseContent(Json, schema); + } + + [Theory] + [MemberData(nameof(InvalidPrimitives))] + public void ValidateResponseContent_InvalidValueType_ThrowsBadRequest( + SchemaValueType expectedType, + string property, + string rawValue) + { + var schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(new Dictionary + { + [property] = new JsonSchemaBuilder().Type(expectedType).Build() + }) + .Required(property) + .Build(); + var json = $"{{\"{property}\": {rawValue} }}"; + + Assert.Throws(() => _sut.ValidateResponseContent(json, schema)); + } + + [Fact] + public void ValidateResponseContent_PropertyTypeStringOrArray_WithString_DoesNotThrow() + { + var schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(new Dictionary + { + ["value"] = new JsonSchemaBuilder().Type(SchemaValueType.String, SchemaValueType.Array).Build() + }) + .Required("value") + .Build(); + + const string Json = "{\"value\": \"hello\"}"; + + _sut.ValidateResponseContent(Json, schema); + } + + [Fact] + public void ValidateResponseContent_PropertyTypeStringOrArray_WithArray_DoesNotThrow() + { + var schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(new Dictionary + { + ["value"] = new JsonSchemaBuilder().Type(SchemaValueType.String, SchemaValueType.Array).Build() + }) + .Required("value") + .Build(); + + const string Json = "{\"value\": [\"one\", \"two\"]}"; + + _sut.ValidateResponseContent(Json, schema); + } + + [Fact] + public void ValidateResponseContent_PropertyTypeStringOrArray_WithNumber_ThrowsBadRequest() + { + var schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(new Dictionary + { + ["value"] = new JsonSchemaBuilder().Type(SchemaValueType.String, SchemaValueType.Array).Build() + }) + .Required("value") + .Build(); + + const string Json = "{\"value\": 123}"; + + Assert.Throws(() => _sut.ValidateResponseContent(Json, schema)); + } + + [Fact] + public void ValidateResponseContent_SchemaMismatch_ThrowsBadRequest() + { + var schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(new Dictionary + { + ["name"] = new JsonSchemaBuilder().Type(SchemaValueType.String).Build() + }) + .Required("name") + .Build(); + + const string Json = "{}"; + + Assert.Throws(() => _sut.ValidateResponseContent(Json, schema)); + } + + [Fact] + public void ValidateResponseContent_InvalidJson_ThrowsBadRequest() + { + var schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Build(); + const string BadJson = "{ not valid json }"; + + Assert.Throws(() => _sut.ValidateResponseContent(BadJson, schema)); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Services/SemanticTreeHandlerTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Services/SemanticTreeHandlerTests.cs new file mode 100644 index 0000000..8530135 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/Services/SemanticTreeHandlerTests.cs @@ -0,0 +1,304 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +using Json.More; +using Json.Schema; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.Submodel.Services; + +public class SemanticTreeHandlerTests +{ + private readonly SemanticTreeHandler _sut; + + private readonly JsonSerializerOptions _options = new() + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + ReadCommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) + } + }; + + public SemanticTreeHandlerTests() + { + var jsonSchemaValidator = Substitute.For(); + _sut = new SemanticTreeHandler(jsonSchemaValidator); + } + + [Fact] + public void GetJson_WithLeafNodeWithStringDataType_ReturnsJsonWithValue() + { + var dataQueryString = JsonNode.Parse(@" + { + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""Name"": { + ""type"": ""string"" + }}, + ""required"": [""Name""], + ""definitions"" : {} + }").AsJsonString(); + var dataQuery = JsonSerializer.Deserialize(dataQueryString, _options); + var leafNodeWithStringDataType = new SemanticLeafNode("Name", DataType.String, "John"); + var expectedLeafWithStringDataTypeJson = JsonNode.Parse(@"{""Name"" : ""John""}")?.AsObject(); + + var result = _sut.GetJson(leafNodeWithStringDataType, dataQuery); + + Assert.Equal(JsonSerializer.Serialize(expectedLeafWithStringDataTypeJson), JsonSerializer.Serialize(result)); + } + + [Fact] + public void GetJson_WithLeafNodeWithBooleanDataType_ReturnsJsonWithValue() + { + var dataQueryString = JsonNode.Parse(@" + { + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""HaveTitle"": { + ""type"": ""boolean"" + }}, + ""required"": [""HaveTitle""], + ""definitions"" : {} + }").AsJsonString(); + var dataQuery = JsonSerializer.Deserialize(dataQueryString, _options); + var leafNodeWithBooleanDataType = new SemanticLeafNode("HaveTitle", DataType.Boolean, "true"); + var expectedLeafWithBooleanDataTypeJson = JsonNode.Parse(@"{""HaveTitle"" : true }")?.AsObject(); + + var result = _sut.GetJson(leafNodeWithBooleanDataType, dataQuery); + + Assert.Equal(JsonSerializer.Serialize(expectedLeafWithBooleanDataTypeJson), JsonSerializer.Serialize(result)); + } + + [Fact] + public void GetJson_WithLeafNodeWithIntegerDataType_ReturnsJsonWithValue() + { + var dataQueryString = JsonNode.Parse(@" + { + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""Weight"": { + ""type"": ""integer"" + }}, + ""required"": [""Weight""], + ""definitions"" : {} + }").AsJsonString(); + var dataQuery = JsonSerializer.Deserialize(dataQueryString, _options); + var leafNodeWithIntergerDataType = new SemanticLeafNode("Weight", DataType.Integer, "22"); + var expectedLeafWithIntergerDataTypeJson = JsonNode.Parse(@"{""Weight"" : 22 }").AsObject(); + + var result = _sut.GetJson(leafNodeWithIntergerDataType, dataQuery); + + Assert.Equal(JsonSerializer.Serialize(expectedLeafWithIntergerDataTypeJson), JsonSerializer.Serialize(result)); + } + + [Fact] + public void GetJson_WithLeafNodeWithNumberDataType_ReturnsJsonWithValue() + { + var dataQueryString = JsonNode.Parse(@" + { + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""Weight"": { + ""type"": ""number"" + }}, + ""required"": [""Weight""], + ""definitions"" : {} + }").AsJsonString(); + var dataQuery = JsonSerializer.Deserialize(dataQueryString, _options); + var leafNodeWithNumberDataType = new SemanticLeafNode("Weight", DataType.Number, "35.485"); + var expectedLeafWithNumberDataTypeJson = JsonNode.Parse(@"{""Weight"" : 35.485 }").AsObject(); + + var result = _sut.GetJson(leafNodeWithNumberDataType, dataQuery); + + Assert.Equal(JsonSerializer.Serialize(expectedLeafWithNumberDataTypeJson), JsonSerializer.Serialize(result)); + } + + [Fact] + public void GetJson_WithLeafNodeWithNoDataType_ReturnsJsonWithValueAsString() + { + var dataQueryString = JsonNode.Parse(@" + { + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""ImageLink"": { + }}, + ""required"": [""ImageLink""], + ""definitions"" : {} + }").AsJsonString(); + var dataQuery = JsonSerializer.Deserialize(dataQueryString, _options); + var leafNodeWithNoDataType = new SemanticLeafNode("ImageLink", DataType.Unknown, "https://www.mm-software.com/fake"); + var expectedLeafWithNoDataTypeJson = JsonNode.Parse(@"{""ImageLink"" : ""https://www.mm-software.com/fake"" }").AsObject(); + + var result = _sut.GetJson(leafNodeWithNoDataType, dataQuery); + + Assert.Equal(JsonSerializer.Serialize(expectedLeafWithNoDataTypeJson), JsonSerializer.Serialize(result)); + } + + [Fact] + public void GetJson_WithSingleChildBranchWithObjectDataType_ReturnsValue() + { + var dataQueryString = JsonNode.Parse(@"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""ContactInformation"": { + ""type"": ""object"", + ""properties"": { + ""Name"": { + ""type"": ""string"" + } + }, + ""required"": [""Name""], + ""definitions"" : {} + }}} + ").AsJsonString(); + var dataQuery = JsonSerializer.Deserialize(dataQueryString, _options); + var singleChildBranchWithObjectDataType = new SemanticBranchNode("ContactInformation", DataType.Object); + singleChildBranchWithObjectDataType.AddChild(new SemanticLeafNode("Name", DataType.String, "John")); + var singleChildBranchWithObjectDataTypeExpectedJson = JsonNode.Parse(@"{ ""ContactInformation"" : { ""Name"" : ""John""} }")!.AsObject(); + + var result = _sut.GetJson(singleChildBranchWithObjectDataType, dataQuery); + + var resultObj = JsonNode.Parse(JsonSerializer.Serialize(result))?.AsObject(); + Assert.Equal(JsonSerializer.Serialize(singleChildBranchWithObjectDataTypeExpectedJson), JsonSerializer.Serialize(result)); + Assert.Single(resultObj?["ContactInformation"]?.AsObject()!); + Assert.Equal("John", resultObj?["ContactInformation"]!["Name"]!.GetValue()); + } + + [Fact] + public void GetJson_WithSingleChildBranchWithArrayDataType_ReturnsValue() + { + var dataQueryString = JsonNode.Parse(@"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""ContactInformation"": { + ""type"": ""array"", + ""properties"": { + ""Name"": { + ""type"": ""string"" + } + }, + ""required"": [""Name""], + ""definitions"" : {} + }}} + ").AsJsonString(); + var dataQuery = JsonSerializer.Deserialize(dataQueryString, _options); + var singleChildBranchWithArrayDataType = new SemanticBranchNode("ContactInformation", DataType.Array); + singleChildBranchWithArrayDataType.AddChild(new SemanticLeafNode("Name", DataType.String, "John")); + var singleChildBranchWithArrayDataTypeExpectedJson = JsonNode.Parse(@"{ ""ContactInformation"" :[ { ""Name"" : ""John""} ] } ")!.AsObject(); + + var result = _sut.GetJson(singleChildBranchWithArrayDataType, dataQuery); + + var resultObj = JsonNode.Parse(JsonSerializer.Serialize(result))!.AsObject(); + Assert.Equal(JsonSerializer.Serialize(singleChildBranchWithArrayDataTypeExpectedJson), JsonSerializer.Serialize(result)); + Assert.Single(resultObj["ContactInformation"]!.AsArray()); + Assert.Equal("John", resultObj["ContactInformation"]![0]!["Name"]!.GetValue()); + } + + [Fact] + public void GetJson_WithNestedArraytypeWithDifferntDataType_ReturnsValue() + { + var dataQueryString = JsonNode.Parse(@"{ + ""$schema"": ""http://json-schema.org/draft-07/schema#"", + ""type"": ""object"", + ""properties"": { + ""ContactInformations"": { + ""type"" : ""object"", + ""properties"":{ + ""ContactInformation"" :{ + ""$ref"": ""#/definitions/ContactInformation"" + } + }, + ""required"": [""ContactInformation""] + }}, + ""definitions"": { + ""ContactInformation"": { + ""type"": ""array"", + ""properties"": { + ""name"": { + ""type"": ""string"" + }, + ""description"": { + ""type"": ""string"" + }, + ""ipCommunication"": { + ""$ref"": ""#/definitions/IPCommunication"" + }}, + ""required"": [""name"", ""description"", ""ipCommunication""] + }, + ""IPCommunication"": { + ""type"": ""array"", + ""properties"": { + ""AvailableTime"": { + ""type"": ""number"" + }}, + ""required"": [""AvailableTime""], + ""additionalProperties"": false + }}}").AsJsonString(); + var dataQuery = JsonSerializer.Deserialize(dataQueryString, _options); + var contactRoot = new SemanticBranchNode("ContactInformations", DataType.Object); + var contactInformation = new SemanticBranchNode("ContactInformation", DataType.Array); + var nameLeaf = new SemanticLeafNode("name", DataType.String, "jone doh"); + var descriptionLeaf = new SemanticLeafNode("description", DataType.String, "this is contact infomation"); + var ipCommunication = new SemanticBranchNode("ipCommunication", DataType.Array); + var availableTimeLeaf = new SemanticLeafNode("AvailableTime", DataType.Number, "9"); + ipCommunication.AddChild(availableTimeLeaf); + contactInformation.AddChild(nameLeaf); + contactInformation.AddChild(descriptionLeaf); + contactInformation.AddChild(ipCommunication); + var contactInformation1 = new SemanticBranchNode("ContactInformation", DataType.Array); + var nameLeaf1 = new SemanticLeafNode("name", DataType.String, "jane"); + var descriptionLeaf1 = new SemanticLeafNode("description", DataType.String, "this is contact infomation"); + var ipCommunication1 = new SemanticBranchNode("ipCommunication", DataType.Array); + var availableTimeLeaf1_1 = new SemanticLeafNode("AvailableTime", DataType.Number, "8"); + var availableTimeLeaf1_2 = new SemanticLeafNode("AvailableTime", DataType.Number, "56"); + ipCommunication1.AddChild(availableTimeLeaf1_1); + ipCommunication1.AddChild(availableTimeLeaf1_2); + contactInformation1.AddChild(nameLeaf1); + contactInformation1.AddChild(descriptionLeaf1); + contactInformation1.AddChild(ipCommunication1); + contactRoot.AddChild(contactInformation); + contactRoot.AddChild(contactInformation1); + var expectedContactJson = JsonNode.Parse(@" + { + ""ContactInformations"":{ + ""ContactInformation"": [ + {""name"": ""jone doh"", + ""description"": ""this is contact infomation"", + ""ipCommunication"":[ { + ""AvailableTime"": 9 + }]}, + {""name"": ""jane"", + ""description"": ""this is contact infomation"", + ""ipCommunication"": [{ + ""AvailableTime"": 8 + },{ + ""AvailableTime"": 56 + }]} + ]}}") + ?.AsObject(); + + var result = _sut.GetJson(contactRoot, dataQuery); + + var resultObj = JsonNode.Parse(JsonSerializer.Serialize(result))!.AsObject(); + Assert.Equal(JsonSerializer.Serialize(expectedContactJson), JsonSerializer.Serialize(result)); + } + + [Fact] + public void GetJson_WithNullNode_ThrowsArgumentException() => Assert.Throws(() => _sut.GetJson(null, null)); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/SubmodelControllerTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/SubmodelControllerTests.cs new file mode 100644 index 0000000..e58872a --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Api/Submodel/SubmodelControllerTests.cs @@ -0,0 +1,77 @@ +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Requests; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; + +using Json.Schema; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.WebUtilities; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Api.Submodel; + +public class SubmodelControllerTests +{ + private readonly ISubmodelHandler _handler = Substitute.For(); + private readonly SubmodelController _sut; + private readonly string _submodelId = "ContactInformation"; + private readonly string _encodedSubmodelId; + private readonly JsonSchema _dataQuery; + private readonly JsonObject _response; + private readonly string _dataQueryString = "{\r\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"https://admin-shell.io/zvei/nameplate/1/0/ContactInformations\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation\": {\r\n \"anyOf\": [\r\n { \"$ref\": \"#/definitions/ContactInformation\" },\r\n {\r\n \"type\": \"array\",\r\n \"items\": { \"$ref\": \"#/definitions/ContactInformation\" }\r\n }\r\n ]\r\n }\r\n },\r\n \"required\": [\r\n \"https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation\"\r\n ]\r\n }\r\n },\r\n \"definitions\": {\r\n \"ContactInformation\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"0173-1#02-AAO204#003\": { \"type\": \"string\" },\r\n \"https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Language\": { \"type\": \"string\" }\r\n },\r\n \"required\": [\r\n \"0173-1#02-AAO204#003\",\r\n \"https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Language\"\r\n ]\r\n }\r\n }\r\n}\r\n"; + + private readonly JsonSerializerOptions _options = new() + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + ReadCommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) + } + }; + + public SubmodelControllerTests() + { + _sut = new SubmodelController(_handler); + _encodedSubmodelId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(_submodelId)); + _response = JsonNode.Parse(@"{""https://admin-shell.io/zvei/nameplate/1/0/ContactInformations"": { + ""https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation"": { + ""0173-1#02-AAO204#003"": ""John Doe"", + ""https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Language"": ""en"" + }}}").AsObject(); + _dataQuery = JsonSerializer.Deserialize(_dataQueryString, _options); + } + + [Fact] + public async Task RetrieveDataAsync_ReturnsBadRequest_WhenComplexDataQueryIsNull() + { + var result = await _sut.RetrieveDataAsync(null, _encodedSubmodelId, CancellationToken.None); + + var badResult = Assert.IsType(result.Result); + + Assert.Equal(ExceptionMessages.InvalidRequestPayload, badResult.Value); + } + + [Fact] + public async Task RetrieveDataAsync_ReturnsOk_WithJsonObject() + { + _handler.GetSubmodelData(Arg.Any(), Arg.Any()) + .Returns(_response); + + var result = await _sut.RetrieveDataAsync(_dataQuery, _encodedSubmodelId, CancellationToken.None); + + var okResult = Assert.IsType(result.Result); + var json = Assert.IsType(okResult.Value); + Assert.Equal(_response.ToJsonString(), json.ToJsonString()); + } +} + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/ApplicationLogic/Services/Manifest/ManifestServiceTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/ApplicationLogic/Services/Manifest/ManifestServiceTests.cs new file mode 100644 index 0000000..9f4b250 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/ApplicationLogic/Services/Manifest/ManifestServiceTests.cs @@ -0,0 +1,36 @@ +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Manifest; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Manifest; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.ApplicationLogic.Services.Manifest; + +public class ManifestServiceTests +{ + private readonly IManifestProvider _manifestProvider = Substitute.For(); + private readonly ManifestService _sut; + + public ManifestServiceTests() => _sut = new ManifestService(_manifestProvider); + + [Fact] + public async Task GetManifestData_ShouldReturnManifestData_FromProvider() + { + var expectedManifest = new ManifestData + { + Capabilities = new CapabilitiesData + { + HasAssetInformation = true, + HasShellDescriptor = false + }, + SupportedSemanticIds = ["semantic1"] + }; + _manifestProvider.GetManifestData().Returns(expectedManifest); + + var result = await _sut.GetManifestData(CancellationToken.None); + + Assert.Equal(expectedManifest, result); + Assert.Equal(expectedManifest.Capabilities.HasAssetInformation, result.Capabilities.HasAssetInformation); + Assert.Equal(expectedManifest.Capabilities.HasShellDescriptor, result.Capabilities.HasShellDescriptor); + Assert.Equal(expectedManifest.SupportedSemanticIds, result.SupportedSemanticIds); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/ApplicationLogic/Services/MetaData/MetaDataServiceTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/ApplicationLogic/Services/MetaData/MetaDataServiceTests.cs new file mode 100644 index 0000000..b7a10ef --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/ApplicationLogic/Services/MetaData/MetaDataServiceTests.cs @@ -0,0 +1,77 @@ +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.MetaData; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +using Microsoft.Extensions.Logging; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.ApplicationLogic.Services.MetaData; + +public class MetaDataServiceTests +{ + private readonly ILogger _logger = Substitute.For>(); + private readonly IMetaDataProvider _repository = Substitute.For(); + private readonly MetaDataService _sut; + private const string AasIdentifier = "ContactInformation"; + + public MetaDataServiceTests() => _sut = new MetaDataService(_logger, _repository); + + [Fact] + public async Task GetShellsAsync_ReturnsShells() + { + var expectedShells = new ShellDescriptorsData(); + _repository.GetShellDescriptorsAsync(null, null, Arg.Any()).Returns(expectedShells); + + var result = await _sut.GetShellDescriptorsAsync(null, null, CancellationToken.None); + + Assert.NotNull(result); + } + + [Fact] + public async Task GetShellElementAsync_ReturnsShellElement() + { + var expectedShell = new ShellDescriptorData(); + _repository.GetShellDescriptorAsync(AasIdentifier, Arg.Any()) + .Returns(expectedShell); + + var result = await _sut.GetShellDescriptorAsync(AasIdentifier, CancellationToken.None); + + Assert.NotNull(result); + Assert.Equal(expectedShell, result); + } + + [Fact] + public async Task GetShellElementAsync_ReturnsNull_WhenNotFound() + { + _repository.GetShellDescriptorAsync(AasIdentifier, Arg.Any())! + .Returns((ShellDescriptorData)null!); + + var result = await _sut.GetShellDescriptorAsync(AasIdentifier, CancellationToken.None); + + Assert.Null(result); + } + + [Fact] + public async Task GetAssetElementAsync_ReturnsAssetElement() + { + var expectedAsset = new AssetData(); + _repository.GetAssetAsync(AasIdentifier, Arg.Any()) + .Returns(expectedAsset); + + var result = await _sut.GetAssetAsync(AasIdentifier, CancellationToken.None); + + Assert.NotNull(result); + Assert.Equal(expectedAsset, result); + } + + [Fact] + public async Task GetAssetElementAsync_ReturnsNull_WhenNotFound() + { + _repository.GetAssetAsync(AasIdentifier, Arg.Any()) + .Returns((AssetData)null!); + + var result = await _sut.GetAssetAsync(AasIdentifier, CancellationToken.None); + + Assert.Null(result); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/ApplicationLogic/Services/Submodel/SubmodelServiceTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/ApplicationLogic/Services/Submodel/SubmodelServiceTests.cs new file mode 100644 index 0000000..d2590b6 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/ApplicationLogic/Services/Submodel/SubmodelServiceTests.cs @@ -0,0 +1,51 @@ +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.ApplicationLogic.Services.Submodel; + +public class SubmodelServiceTests +{ + private readonly SemanticBranchNode _sampleInputTree; + private readonly SemanticBranchNode _enrichedTree; + private readonly ISubmodelProvider _repository; + private readonly SubmodelService _sut; + private const string SubmodelId = "ContactInformation"; + + public SubmodelServiceTests() + { + _repository = Substitute.For(); + _sut = new SubmodelService(_repository); + _sampleInputTree = new SemanticBranchNode("ContactInformation", DataType.Object); + _sampleInputTree.AddChild(new SemanticLeafNode("Name", DataType.String, "")); + _enrichedTree = new SemanticBranchNode("ContactInformation", DataType.Object); + _enrichedTree.AddChild(new SemanticLeafNode("Name", DataType.String, "John")); + } + + [Fact] + public async Task GetProductDataAsync_ReturnsEnricSematicTree_OnHappyPath() + { + _repository + .EnrichWithData(_sampleInputTree, SubmodelId) + .Returns(_enrichedTree); + + var actual = await _sut.GetValuesBySemanticIds(_sampleInputTree, SubmodelId); + + Assert.Equal(actual, _enrichedTree); + _repository.Received(1).EnrichWithData(_sampleInputTree, SubmodelId); + } + + [Fact] + public async Task GetProductDataAsync_ReturnsSameTreeNode_WhenValueNotFound() + { + _repository + .EnrichWithData(_sampleInputTree, SubmodelId) + .Returns(_sampleInputTree); + + var actual = await _sut.GetValuesBySemanticIds(_sampleInputTree, SubmodelId); + + Assert.Equal(_sampleInputTree, actual); + _repository.Received(1).EnrichWithData(_sampleInputTree, SubmodelId); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/CleanArchitectureTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/CleanArchitectureTests.cs new file mode 100644 index 0000000..c2f60b8 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/CleanArchitectureTests.cs @@ -0,0 +1,129 @@ +using ArchUnitNET.Domain; +using ArchUnitNET.Loader; +using ArchUnitNET.xUnit; + +using static ArchUnitNET.Fluent.ArchRuleDefinition; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests; + +/// +/// This validates that the Onion Architecture as described in the SAD is not broken. +/// https://dev.azure.com/mm-products/AAS.TwinEngine/_wiki/wikis/Wiki/163/5-Solution-Strategy +/// +public class CleanArchitectureTests +{ + private const string BaseNamespace = "AAS.TwinEngine.Plugin.TestPlugin"; + + private readonly Architecture _architecture; + private readonly IObjectProvider _apiLayer; + private readonly IObjectProvider _applicationLogicLayer; + private readonly IObjectProvider _domainModelLayer; + private readonly IObjectProvider _infrastructureLayer; + + public CleanArchitectureTests() + { + _architecture = new ArchLoader().LoadAssemblies(System.Reflection.Assembly.Load(BaseNamespace)).Build(); + + _apiLayer = Types().That().ResideInNamespace($"{BaseNamespace}.Api.*", true).As("Api"); + _applicationLogicLayer = Types().That().ResideInNamespace($"{BaseNamespace}.ApplicationLogic.*", true).As("ApplicationLogic"); + _domainModelLayer = Types().That().ResideInNamespace($"{BaseNamespace}.DomainModel*", true).As("DomainModel"); + _infrastructureLayer = Types().That().ResideInNamespace($"{BaseNamespace}.Infrastructure.*", true).As("Infrastructure"); + } + + [Fact] + public void DomainModelShallNotHaveExternalDependencies() + { + var forbiddenTypes = new List(); + forbiddenTypes.AddRange(_infrastructureLayer.GetObjects(_architecture)); + forbiddenTypes.AddRange(_apiLayer.GetObjects(_architecture)); + forbiddenTypes.AddRange(_applicationLogicLayer.GetObjects(_architecture)); + + Types().That().Are(_domainModelLayer) + .Should() + .NotDependOnAny(Types().That().Are(forbiddenTypes)) + .Check(_architecture); + } + + [Fact] + public void ApplicationLogicShallNotHaveDependenciesToInfrastructure() + { + Types().That().Are(_applicationLogicLayer) + .Should() + .NotDependOnAny(Types().That().Are(_infrastructureLayer)) + .Check(_architecture); + } + + [Fact] + public void ApplicationLogicShallNotHaveDependenciesToApi() + { + Types().That().Are(_applicationLogicLayer) + .Should() + .NotDependOnAny(Types().That().Are(_apiLayer)) + .Check(_architecture); + } + + [Fact] + public void InfrastructureShallNotHaveDependenciesToApi() + { + Types().That().Are(_infrastructureLayer) + .Should() + .NotDependOnAny(Types().That().Are(_apiLayer)) + .Check(_architecture); + } + + [Fact] + public void ApiShallNotHaveDependenciesToInfrastructure() + { + Types().That().Are(_apiLayer) + .Should() + .NotDependOnAny(Types().That().Are(_infrastructureLayer)) + .Check(_architecture); + } + + [Fact] + public void RepositoryClassesShallBeInCorrectNamespace() + { + Classes().That().HaveNameEndingWith("Repository").Should() + .ResideInNamespace($"{BaseNamespace}.Infrastructure.DataAccess*", true) + .WithoutRequiringPositiveResults() + .Check(_architecture); + } + + [Fact] + public void RepositoryInterfacesShallBeInCorrectNamespace() + { + Interfaces().That() + .HaveNameEndingWith("Repository") + .And() + .DoNotHaveFullName($"{BaseNamespace}.Infrastructure.DataAccess.GenericRepository.IMongoDbRepository") + .Should() + .ResideInNamespace($"{BaseNamespace}.ApplicationLogic.*", true) + .WithoutRequiringPositiveResults() + .Check(_architecture); + } + + [Fact] + public void ServicesShallBeInCorrectNamespace() + { + Classes().That().HaveNameEndingWith("Service").Should() + .ResideInNamespace($"{BaseNamespace}.ApplicationLogic.Service.*", true) + .Check(_architecture); + } + + [Fact] + public void ServiceInterfacesShallBeInCorrectNamespace() + { + Interfaces().That().HaveNameEndingWith("Service") + .Should() + .ResideInNamespace($"{BaseNamespace}.ApplicationLogic.Service.*", true) + .Check(_architecture); + } + + [Fact] + public void ControllerShallBeInCorrectNamespace() + { + Classes().That().HaveNameEndingWith("Controller").Should() + .ResideInNamespace($"{BaseNamespace}.Api.*", true) + .Check(_architecture); + } +} \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/DataAccess/MapperProfiles/AssetMappingProfileTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/DataAccess/MapperProfiles/AssetMappingProfileTests.cs new file mode 100644 index 0000000..fcd3ac8 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/DataAccess/MapperProfiles/AssetMappingProfileTests.cs @@ -0,0 +1,46 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.DataAccess.Entity; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.DataAccess.MapperProfiles; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Infrastructure.DataAccess.MapperProfiles; + +public class AssetMappingProfileTests +{ + [Fact] + public void ToDomainModel_MapsAllFields_WhenThumbnailIsPresent() + { + const string GlobalAssetId = "asset-123"; + var entity = new AssetInformationDataEntity { DefaultThumbnail = new DefaultThumbnailDataEntity { ContentType = "image/png", Path = "/images/thumb.png" }, GlobalAssetId = GlobalAssetId }; + var specificAssetIds = new List + { + new() { Name = "SerialNumber", Value = "SN123" } + }; + + var result = entity.ToDomainModel(GlobalAssetId, specificAssetIds); + + Assert.NotNull(result); + Assert.Equal(GlobalAssetId, result.GlobalAssetId); + Assert.Equal(specificAssetIds, result.SpecificAssetIds); + Assert.NotNull(result.DefaultThumbnail); + Assert.Equal("image/png", result.DefaultThumbnail.ContentType); + Assert.Equal("/images/thumb.png", result.DefaultThumbnail.Path); + } + + [Fact] + public void ToDomainModel_SetsDefaultThumbnailToNull_WhenThumbnailIsMissing() + { + var entity = new AssetInformationDataEntity + { + DefaultThumbnail = null + }; + const string GlobalAssetId = "asset-456"; + var specificAssetIds = new List(); + + var result = entity.ToDomainModel(GlobalAssetId, specificAssetIds); + + Assert.NotNull(result); + Assert.Equal(GlobalAssetId, result.GlobalAssetId); + Assert.Equal(specificAssetIds, result.SpecificAssetIds); + Assert.Null(result.DefaultThumbnail); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/DataAccess/MapperProfiles/ShellDescriptorMappingProfileTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/DataAccess/MapperProfiles/ShellDescriptorMappingProfileTests.cs new file mode 100644 index 0000000..87c4d75 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/DataAccess/MapperProfiles/ShellDescriptorMappingProfileTests.cs @@ -0,0 +1,62 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.DataAccess.Entity; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.DataAccess.MapperProfiles; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Infrastructure.DataAccess.MapperProfiles; + +public class ShellDescriptorMappingProfileTests +{ + [Fact] + public void MapToDomainModel_MapsAllFieldsCorrectly() + { + var entity = new MetaDataEntity + { + Id = "shell-001", + GlobalAssetId = "asset-001", + IdShort = "Shell001", + SpecificAssetIds = + [ + new SpecificAssetIdEntity { Name = "SerialNumber", Value = "SN001" }, + new SpecificAssetIdEntity { Name = "PartNumber", Value = "PN001" } + ] + }; + + var result = entity.MapToDomainModel(); + + Assert.NotNull(result); + Assert.Equal(entity.Id, result.Id); + Assert.Equal(entity.GlobalAssetId, result.GlobalAssetId); + Assert.Equal(entity.IdShort, result.IdShort); + Assert.Equal(2, result.SpecificAssetIds!.Count); + Assert.Equal("SerialNumber", result.SpecificAssetIds[0].Name); + Assert.Equal("SN001", result.SpecificAssetIds[0].Value); + } + + [Fact] + public void MapToDomainModel_HandlesNullSpecificAssetIds() + { + var entity = new MetaDataEntity + { + Id = "shell-002", + GlobalAssetId = "asset-002", + IdShort = "Shell002", + SpecificAssetIds = null + }; + + var result = entity.MapToDomainModel(); + + Assert.NotNull(result); + Assert.Equal(entity.Id, result.Id); + Assert.Empty(result.SpecificAssetIds!); + } + + [Fact] + public void ToDomainModelList_ReturnsEmptyList_WhenInputIsNull() + { + List? entities = null; + + var result = entities!.ToDomainModelList(); + + Assert.NotNull(result); + Assert.Empty(result); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/ManifestProvider/Helper/JsonConverterTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/ManifestProvider/Helper/JsonConverterTests.cs new file mode 100644 index 0000000..317cdeb --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/ManifestProvider/Helper/JsonConverterTests.cs @@ -0,0 +1,150 @@ +using System.Text.Json; + +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.ManifestProvider.Helper; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Infrastructure.Providers.ManifestProvider.Helper; + +public class JsonConverterTests +{ + [Fact] + public void ParseJson_ShouldReturnBranchNode_WhenJsonIsObject() + { + const string Json = """ + { + "name": "value" + } + """; + using var doc = JsonDocument.Parse(Json); + + var result = JsonConverter.ParseJson(doc); + + Assert.IsType(result); + var branch = (SemanticBranchNode)result; + Assert.Single(branch.Children); + Assert.Equal("name", branch.Children[0].SemanticId); + } + + [Fact] + public void ParseJson_ShouldParseNestedJsonString_WhenRootIsString() + { + const string Json = """ + { + "items": { + "item":{ + "key" : "value" + } + } + } + """; + using var doc = JsonDocument.Parse(Json); + + var result = JsonConverter.ParseJson(doc); + + Assert.IsType(result); + var branch = (SemanticBranchNode)result; + Assert.Single(branch.Children); + Assert.Equal("item", branch.Children[0].SemanticId); + } + + [Fact] + public void ParseJson_ShouldReturnArrayBranch_WhenJsonIsArray() + { + const string Json = "[{\"item\": 1}, {\"item\": 2}]"; + using var doc = JsonDocument.Parse(Json); + + var result = JsonConverter.ParseJson(doc); + + Assert.IsType(result); + var branch = (SemanticBranchNode)result; + Assert.Equal(2, branch.Children.Count); + } + + [Fact] + public void ParseJson_ShouldReturnArrayBranch_WhenJsonIsMultipleArray() + { + const string Json = "[{\"item\": 1}, {\"item\": 2}, {\"item\": 3}]"; + using var doc = JsonDocument.Parse(Json); + + var result = JsonConverter.ParseJson(doc); + + Assert.IsType(result); + var branch = (SemanticBranchNode)result; + Assert.Equal(3, branch.Children.Count); + } + + [Fact] + public void ParseJson_ShouldHandleEmptyObject() + { + const string Json = "{}"; + using var doc = JsonDocument.Parse(Json); + + var result = JsonConverter.ParseJson(doc); + + Assert.IsType(result); + var branch = (SemanticBranchNode)result; + Assert.Empty(branch.Children); + } + + [Fact] + public void ParseJson_ShouldProcessArrayInsideObjectProperty() + { + const string Json = """ + { + "items": [ + { "id": 1 }, + { "id": 2 } + ] + } + """; + using var doc = JsonDocument.Parse(Json); + + var result = JsonConverter.ParseJson(doc); + + Assert.IsType(result); + var branch = (SemanticBranchNode)result; + Assert.Equal(2, branch.Children.Count); + + var itemsBranch = branch.Children[0] as SemanticBranchNode; + Assert.NotNull(itemsBranch); + Assert.Equal("items", itemsBranch.SemanticId); + Assert.Single(itemsBranch.Children); + } + + [Fact] + public void ParseJson_ShouldProcessArrayAsSinglePropertyValue() + { + const string Json = """ + { + "data": [ + { "name": "A" }, + { "name": "B" } + ] + } + """; + using var doc = JsonDocument.Parse(Json); + + var result = JsonConverter.ParseJson(doc); + + Assert.IsType(result); + var branch = (SemanticBranchNode)result; + Assert.Equal("data", branch.SemanticId); + Assert.Equal(2, branch.Children.Count); + + var firstItem = branch.Children[0] as SemanticBranchNode; + Assert.NotNull(firstItem); + Assert.Single(firstItem.Children); + Assert.Equal("name", firstItem.Children[0].SemanticId); + } + + [Fact] + public void ParseJson_ShouldHandleNullString() + { + const string Json = "null"; + using var doc = JsonDocument.Parse(Json); + + var result = JsonConverter.ParseJson(doc); + + Assert.IsType(result); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/ManifestProvider/ManifestProviderTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/ManifestProvider/ManifestProviderTests.cs new file mode 100644 index 0000000..4ad12d2 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/ManifestProvider/ManifestProviderTests.cs @@ -0,0 +1,82 @@ +using System.Text.Json; + +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.Config; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +using NSubstitute; + +using Provider = AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.ManifestProvider.ManifestProvider; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Infrastructure.Providers.ManifestProvider; + +public class ManifestProviderTests +{ + private readonly ILogger _logger; + private readonly Provider _sut; + + public ManifestProviderTests() + { + _logger = Substitute.For>(); + var capabilities = Substitute.For>(); + capabilities.Value.Returns(new Capabilities { HasAssetInformation = true, HasShellDescriptor = true }); + _sut = new Provider(_logger, capabilities); + } + + private static void SetSubmodelData(string jsonContent) => MockData.SubmodelData = JsonDocument.Parse(jsonContent); + + [Fact] + public void GetManifestData_ValidResource_ReturnsExpectedSemanticIdsAndCapabilities() + { + SetSubmodelData(TestData.TestSubmodelData); + + var manifest = _sut.GetManifestData(); + + Assert.NotNull(manifest); + Assert.NotNull(manifest.SupportedSemanticIds); + Assert.NotEmpty(manifest.SupportedSemanticIds); + Assert.Contains("Email", manifest.SupportedSemanticIds); + Assert.Contains("TelephoneNumber", manifest.SupportedSemanticIds); + Assert.Contains("ClassId", manifest.SupportedSemanticIds); + Assert.Contains("StatusValue", manifest.SupportedSemanticIds); + Assert.NotNull(manifest.Capabilities); + Assert.True(manifest.Capabilities.HasAssetInformation); + Assert.True(manifest.Capabilities.HasShellDescriptor); + } + + [Fact] + public void GetManifestData_EmptyArrayResource_ReturnsNoSupportedSemanticIds() + { + SetSubmodelData("{}"); + + var manifest = _sut.GetManifestData(); + + Assert.NotNull(manifest); + Assert.NotNull(manifest.SupportedSemanticIds); + Assert.Empty(manifest.SupportedSemanticIds); + } + + [Fact] + public void GetManifestData_WithCapabilitiesFalse_ReturnsCorrectCapabilities() + { + const string ValidSubmodelData = @"{ + ""test-submodelId"": { + ""Email"": ""test@example.com"" + } + }"; + + SetSubmodelData(ValidSubmodelData); + var capabilities = Substitute.For>(); + capabilities.Value.Returns(new Capabilities { HasAssetInformation = false, HasShellDescriptor = false }); + var sut = new Provider(_logger, capabilities); + + var manifest = sut.GetManifestData(); + + Assert.NotNull(manifest); + Assert.NotNull(manifest.Capabilities); + Assert.False(manifest.Capabilities.HasAssetInformation); + Assert.False(manifest.Capabilities.HasShellDescriptor); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/MetaDataProvider/Helper/PaginatorTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/MetaDataProvider/Helper/PaginatorTests.cs new file mode 100644 index 0000000..018d7de --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/MetaDataProvider/Helper/PaginatorTests.cs @@ -0,0 +1,121 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Common.Extensions; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.MetaDataProvider.Helper; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Infrastructure.Providers.MetaDataProvider.Helper; + +public class PaginatorTests +{ + private readonly Func _idSelector; + private readonly List _items; + + public PaginatorTests() + { + _idSelector = i => i.Id; + _items = Enumerable.Range(1, 20) + .Select(i => new TestItem { Id = $"id{i}", Value = $"value{i}" }) + .ToList(); + } + + [Fact] + public void GetPagedResult_ShouldReturnFirstPage_WhenNoCursorProvided() + { + var (pagedItems, meta) = Paginator.GetPagedResult(_items, _idSelector, 5, null); + + Assert.Equal(5, pagedItems.Count); + Assert.Equal("id1", pagedItems.First().Id); + Assert.NotNull(meta.Cursor); + } + + [Fact] + public void GetPagedResult_ShouldReturnNextPage_WhenValidCursorProvided() + { + var cursor = "id5".EncodeToBase64(); + var (pagedItems, meta) = Paginator.GetPagedResult(_items, _idSelector, 5, cursor); + + Assert.Equal("id6", pagedItems.First().Id); + Assert.Equal(5, pagedItems.Count); + Assert.NotNull(meta.Cursor); + } + + [Fact] + public void GetPagedResult_ShouldReturnFromStart_WhenInvalidCursorProvided() + { + var cursor = "nonExistingId".EncodeToBase64(); + var (pagedItems, meta) = Paginator.GetPagedResult(_items, _idSelector, 5, cursor); + + Assert.Equal("id1", pagedItems.First().Id); + Assert.NotNull(meta.Cursor); + } + + [Fact] + public void GetPagedResult_ShouldReturnDefaultPageSize_WhenPageSizeIsNull() + { + var (pagedItems, meta) = Paginator.GetPagedResult(_items, _idSelector, null, null); + + Assert.Equal(20, pagedItems.Count); + } + + [Fact] + public void GetPagedResult_ShouldReturnDefaultPageSize_WhenTotalItemIsGreaterThenPageSize_WithCursorAsLastItem() + { + var items = Enumerable.Range(1, 200) + .Select(i => new TestItem { Id = $"id{i}", Value = $"value{i}" }) + .ToList(); + var expectedCursor = "id100".EncodeToBase64(); + + var (pagedItems, meta) = Paginator.GetPagedResult(items, _idSelector, null, null); + + Assert.Equal(100, pagedItems.Count); + Assert.Equal(expectedCursor, meta.Cursor); + } + + [Fact] + public void GetPagedResult_ShouldCapPageSizeAtMax_WhenPageSizeExceedsLimit() + { + var expectedCursor = "id20".EncodeToBase64(); + + var (pagedItems, meta) = Paginator.GetPagedResult(_items, _idSelector, 2000, null); + + Assert.Equal(20, pagedItems.Count); + Assert.Equal(expectedCursor, meta.Cursor); + } + + [Fact] + public void GetPagedResult_ShouldUseZeroPageSize_WhenPageSizeIsZeroOrNegative() + { + var (pagedItems1, _) = Paginator.GetPagedResult(_items, _idSelector, 0, null); + var (pagedItems2, _) = Paginator.GetPagedResult(_items, _idSelector, -10, null); + + Assert.Equal(0, pagedItems1.Count); + Assert.Equal(0, pagedItems2.Count); + } + + [Fact] + public void GetPagedResult_ShouldReturnLastIdAsCursor_WhenAtEndOfList() + { + var cursor = "id16".EncodeToBase64(); + var expectedCursor = "id20".EncodeToBase64(); + var (pagedItems, meta) = Paginator.GetPagedResult(_items, _idSelector, 10, cursor); + + Assert.Equal(4, pagedItems.Count); + Assert.Equal(expectedCursor, meta.Cursor); + } + + [Fact] + public void GetPagedResult_ShouldReturnLastIdAsCursor_WhenNotFullPage() + { + var cursor = "id18".EncodeToBase64(); + var expectedCursor = "id20".EncodeToBase64(); + var (pagedItems, meta) = Paginator.GetPagedResult(_items, _idSelector, 5, cursor); + + Assert.Equal(2, pagedItems.Count); + Assert.Equal(expectedCursor, meta.Cursor); + } + + private class TestItem + { + public string Id { get; set; } = null!; + public string Value { get; set; } = null!; + } +} + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/MetaDataProvider/MetaDataProviderTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/MetaDataProvider/MetaDataProviderTests.cs new file mode 100644 index 0000000..f8556a0 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/MetaDataProvider/MetaDataProviderTests.cs @@ -0,0 +1,149 @@ +using System.Text.Json; + +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers; + +using Microsoft.Extensions.Logging; + +using NSubstitute; + +using Provider = AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.MetaDataProvider.MetaDataProvider; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Infrastructure.Providers.MetaDataProvider; + +public class MetaDataProviderTests +{ + private readonly ILogger _logger = Substitute.For>(); + private readonly Provider _sut; + + public MetaDataProviderTests() + { + SetMetaData(TestData.TestMetaData); + _sut = new Provider(_logger); + } + + private static void SetMetaData(string jsonContent) => MockData.MetaData = JsonDocument.Parse(jsonContent); + + [Fact] + public async Task GetShellDescriptorsAsync_ReturnsPagedShells_WithPagingMetadata() + { + const int Limit = 2; + string? cursor = null; + + var result = await _sut.GetShellDescriptorsAsync(Limit, cursor, CancellationToken.None); + + Assert.NotNull(result); + Assert.NotNull(result.Result); + Assert.True(result.Result.Count <= Limit); + Assert.NotNull(result.PagingMetaData); + Assert.False(string.IsNullOrWhiteSpace(result.PagingMetaData.Cursor)); + } + + [Fact] + public async Task GetShellDescriptorsAsync_ReturnsCorrectPage_WhenCursorIsProvided() + { + var firstPage = await _sut.GetShellDescriptorsAsync(2, null, CancellationToken.None); + var nextCursor = firstPage.PagingMetaData?.Cursor; + + var secondPage = await _sut.GetShellDescriptorsAsync(2, nextCursor, CancellationToken.None); + + Assert.NotNull(secondPage); + Assert.NotNull(secondPage.Result); + Assert.True(secondPage.Result.Count <= 2); + Assert.NotEqual(firstPage.Result?.FirstOrDefault()?.Id, secondPage.Result?.FirstOrDefault()?.Id); + } + + [Fact] + public async Task GetShellDescriptorsAsync_ThrowsNotFound_WhenCursorIsInvalid() + { + var record = await Assert.ThrowsAsync(() => _sut.GetShellDescriptorsAsync(2, "bW0=", CancellationToken.None)); + + Assert.Equal(ExceptionMessages.ShellDescriptorDataNotFound, record.Message); + } + + [Fact] + public async Task GetShellDescriptorsAsync_NeverReturnsShell_WithEmptyIds() + { + var result = await _sut.GetShellDescriptorsAsync(null, null, CancellationToken.None); + + Assert.NotNull(result); + Assert.All(result.Result!, shell => Assert.False(string.IsNullOrWhiteSpace(shell.Id), "Shell with empty or null Id found.")); + } + + [Fact] + public async Task GetShellDescriptorsAsync_ReturnsEmptyList_WhenNoShellsExist() + { + SetMetaData("[]"); + var sut = new Provider(_logger); + + var result = await sut.GetShellDescriptorsAsync(null, null, CancellationToken.None); + + Assert.NotNull(result); + Assert.Empty(result.Result ?? []); + Assert.NotNull(result.PagingMetaData); + Assert.Null(result.PagingMetaData.Cursor); + } + + [Fact] + public async Task GetShellDescriptorAsync_ReturnsCorrectShell() + { + const string Id = "ContactInformation"; + + var result = await _sut.GetShellDescriptorAsync(Id, CancellationToken.None); + + Assert.NotNull(result); + Assert.Equal(Id, result.Id); + } + + [Fact] + public async Task GetShellDescriptorAsync_ReturnsCorrectShell_HasEmptyIdShort() + { + const string Id = "1000-859"; + + var result = await _sut.GetShellDescriptorAsync(Id, CancellationToken.None); + + Assert.NotNull(result); + Assert.Equal(Id, result.Id); + Assert.Empty(result.IdShort); + } + + [Fact] + public async Task GetShellDescriptorAsync_ThrowsNotFoundException_WhenIdNotFound() + { + const string InvalidId = "nonexistent-id"; + + await Assert.ThrowsAsync(() => _sut.GetShellDescriptorAsync(InvalidId, CancellationToken.None)); + } + + [Fact] + public async Task GetAssetAsync_ReturnsAsset_WhenExists() + { + const string AssetId = "ContactInformation"; + + var result = await _sut.GetAssetAsync(AssetId, CancellationToken.None); + + Assert.NotNull(result); + Assert.Equal(AssetId, result.GlobalAssetId); + } + + [Fact] + public async Task GetAssetAsync_ThrowsNotFoundException_WhenShellFound_ButDoesNotHaveAssetInformation() + { + const string InvalidAssetId = "SoftwareNameplate"; + + var exception = await Assert.ThrowsAsync(() => + _sut.GetAssetAsync(InvalidAssetId, CancellationToken.None)); + Assert.Contains(ExceptionMessages.AssetNotFound, exception.Message, StringComparison.CurrentCulture); + } + + [Fact] + public async Task GetAssetAsync_ThrowsNotFoundException_WhenAssetNotFound() + { + const string InvalidAssetId = "nonexistent-asset"; + + var exception = await Assert.ThrowsAsync(() => + _sut.GetAssetAsync(InvalidAssetId, CancellationToken.None)); + Assert.Contains(ExceptionMessages.AssetNotFound, exception.Message, StringComparison.CurrentCulture); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/MockDataInitializerTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/MockDataInitializerTests.cs new file mode 100644 index 0000000..6f7fe15 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/MockDataInitializerTests.cs @@ -0,0 +1,87 @@ +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers; + +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Infrastructure.Providers; + +public class MockDataInitializerTests +{ + private readonly ILogger _logger; + private readonly IHostEnvironment _hostEnvironment; + private readonly string _testDataDirectory; + + public MockDataInitializerTests() + { + _logger = Substitute.For>(); + _hostEnvironment = Substitute.For(); + _testDataDirectory = CreateTestDataDirectory(); + _hostEnvironment.ContentRootPath.Returns(_testDataDirectory); + } + + private static string CreateTestDataDirectory() + { + var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + var dataDir = Path.Combine(tempDir, "Data"); + Directory.CreateDirectory(dataDir); + return tempDir; + } + + private void CreateTestFile(string fileName, string content) + { + var filePath = Path.Combine(_testDataDirectory, "Data", fileName); + File.WriteAllText(filePath, content); + } + + [Fact] + public void Initialize_ValidFiles_LoadsDataIntoRegistry() + { + CreateTestFile("mock-metadata.json", "{ \"meta\": \"data\" }"); + CreateTestFile("mock-submodel-data.json", "{ \"submodel\": \"data\" }"); + + var initializer = new MockDataInitializer(_hostEnvironment, _logger); + initializer.Initialize(CancellationToken.None); + + Assert.NotNull(MockData.MetaData); + Assert.NotNull(MockData.SubmodelData); + } + + [Fact] + public void Initialize_MissingMetadataFile_ThrowsFileNotFoundException() + { + CreateTestFile("mock-submodel-data.json", "{ \"submodel\": \"data\" }"); + + var initializer = new MockDataInitializer(_hostEnvironment, _logger); + + var ex = Assert.Throws(() => initializer.Initialize(CancellationToken.None)); + Assert.Contains("file not found", ex.Message, StringComparison.Ordinal); + } + + [Fact] + public void Initialize_InvalidJson_ThrowsInternalServerException() + { + CreateTestFile("mock-metadata.json", "{ invalid json"); + CreateTestFile("mock-submodel-data.json", "{ \"submodel\": \"data\" }"); + + var initializer = new MockDataInitializer(_hostEnvironment, _logger); + + var ex = Assert.Throws(() => initializer.Initialize(CancellationToken.None)); + Assert.Contains(ExceptionMessages.ResourceNotValid, ex.Message, StringComparison.Ordinal); + } + + [Fact] + public void Initialize_EmptyFile_ThrowsInternalServerException() + { + CreateTestFile("mock-metadata.json", ""); + CreateTestFile("mock-submodel-data.json", "{ \"submodel\": \"data\" }"); + + var initializer = new MockDataInitializer(_hostEnvironment, _logger); + + var ex = Assert.Throws(() => initializer.Initialize(CancellationToken.None)); + Assert.Contains(ExceptionMessages.ResourceNotValid, ex.Message, StringComparison.Ordinal); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/SubmodelProviders/SubmodelProviderTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/SubmodelProviders/SubmodelProviderTests.cs new file mode 100644 index 0000000..daab9e5 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/SubmodelProviders/SubmodelProviderTests.cs @@ -0,0 +1,253 @@ +using System.Text.Json; + +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel.Config; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.SubmodelProviders; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +using NSubstitute; + +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Infrastructure.Providers.SubmodelProviders; + +public class SubmodelProviderTests +{ + private readonly ILogger _logger; + private readonly SubmodelProvider _sut; + private readonly IOptions _semantics; + private const string ProductId = "test-submodelId"; + + public SubmodelProviderTests() + { + _logger = Substitute.For>(); + _semantics = Substitute.For>(); + _semantics.Value.Returns(new Semantics { IndexContextPrefix = "_aastwinengine_" }); + SetSubmodelData(TestData.TestSubmodelData); + _sut = new SubmodelProvider(_logger, _semantics); + } + + private static void SetSubmodelData(string jsonContent) => MockData.SubmodelData = JsonDocument.Parse(jsonContent); + + [Fact] + public void EnrichWithData_LeafNode_SetsValueFromJson() + { + var leaf = new SemanticLeafNode("Email", DataType.String, null); + var root = new SemanticBranchNode("root", DataType.Object); + root.AddChild(leaf); + + _sut.EnrichWithData(root, ProductId); + + Assert.Equal("test@example.com", leaf.Value); + } + + [Fact] + public void EnrichWithData_LeafNode_WhenNoDetailsAvailable_SetsEmptyValue() + { + var name = new SemanticLeafNode("name", DataType.String, null!); + + _sut.EnrichWithData(name, ProductId); + + Assert.Equal(string.Empty, name.Value); + } + + [Fact] + public void EnrichWithData_BranchNodeWithoutLeaves_ReturnsUnmodifiedBranch() + { + var branch = new SemanticBranchNode("contactInformation", DataType.Object); + + var result = _sut.EnrichWithData(branch, ProductId); + + Assert.Empty(branch.Children); + Assert.Same(branch, result); + } + + [Fact] + public void EnricWithData_ForLeafWithoutComplexData_SetsValueFromData() + { + var root = new SemanticBranchNode("ManufacturerName", DataType.Object); + var leaf = new SemanticLeafNode("ManufacturerName_en", DataType.String, null!); + root.AddChild(leaf); + + _sut.EnrichWithData(root, ProductId); + + Assert.Equal("M&M", leaf.Value); + } + + [Fact] + public void EnrichWithData_ForBranchNodeWithoutComplexData_SetsValueFormData() + { + var contact = new SemanticBranchNode("ContactInformation", DataType.Object); + contact.AddChild(new SemanticLeafNode("Email", DataType.String, null!)); + contact.AddChild(new SemanticLeafNode("Phone", DataType.String, null!)); + + _sut.EnrichWithData(contact, ProductId); + + var email = (SemanticLeafNode)contact.Children + .First(c => c.SemanticId == "Email"); + var phone = (SemanticLeafNode)contact.Children + .First(c => c.SemanticId == "Phone"); + Assert.Equal("contact@test.com", email.Value); + Assert.Equal("555-1234", phone.Value); + } + + [Fact] + public void EnrichWithData_ForBranchNodeWithComplexData_ClonesChildrenForEachElement_SetsValueFormData() + { + var contactInformationBranch = new SemanticBranchNode("ContactInformations", DataType.Object); + var contactBranch = new SemanticBranchNode("ContactInformation", DataType.Array); + contactBranch.AddChild(new SemanticLeafNode("Email", DataType.String, null!)); + var phoneBranch = new SemanticBranchNode("Phone", DataType.Object); + contactBranch.AddChild(phoneBranch); + phoneBranch.AddChild(new SemanticLeafNode("TelephoneNumber", DataType.String, "")); + contactInformationBranch.AddChild(contactBranch); + + _sut.EnrichWithData(contactInformationBranch, ProductId); + + var contacts = contactInformationBranch.Children.OfType().ToList(); + Assert.Equal(2, contacts.Count); + var firstContact = contacts[0]; + var firstEmail = firstContact.Children.First(c => c.SemanticId == "Email") as SemanticLeafNode; + var firstPhone = firstContact.Children.First(c => c.SemanticId == "Phone") as SemanticBranchNode; + var phoneTelephoneNumber1 = firstPhone.Children.First(c => c.SemanticId == "TelephoneNumber") as SemanticLeafNode; + Assert.Equal("first@test.com", firstEmail!.Value); + Assert.Equal("111-1111", phoneTelephoneNumber1.Value); + var secondContact = contacts[1]; + var secondEmail = secondContact.Children.First(c => c.SemanticId == "Email") as SemanticLeafNode; + var secondPhone = secondContact.Children.First(c => c.SemanticId == "Phone") as SemanticBranchNode; + var phoneTelephoneNumber2 = secondPhone.Children.First(c => c.SemanticId == "TelephoneNumber") as SemanticLeafNode; + Assert.Equal("second@test.com", secondEmail!.Value); + Assert.Equal("222-2222", phoneTelephoneNumber2!.Value); + } + + [Fact] + public void EnrichWithData_ForBranchNodeWithSpecificIndex_SetsValueFormData() + { + var contactInformationBranch = new SemanticBranchNode("ContactInformations", DataType.Object); + var contactBranch = new SemanticBranchNode("ContactInformation_aastwinengine_01", DataType.Object); + contactBranch.AddChild(new SemanticLeafNode("Email", DataType.String, null!)); + var phoneBranch = new SemanticBranchNode("Phone", DataType.Object); + contactBranch.AddChild(phoneBranch); + phoneBranch.AddChild(new SemanticLeafNode("TelephoneNumber", DataType.String, "")); + var availableBranch = new SemanticBranchNode("AvailableTime", DataType.Object); + phoneBranch.AddChild(availableBranch); + availableBranch.AddChild(new SemanticLeafNode("AvailableTime_de", DataType.String, "")); + contactInformationBranch.AddChild(contactBranch); + + _sut.EnrichWithData(contactInformationBranch, ProductId); + + var email = contactBranch.Children.First(c => c.SemanticId == "Email") as SemanticLeafNode; + var phone = contactBranch.Children.First(c => c.SemanticId == "Phone") as SemanticBranchNode; + var phoneTelephoneNumber = phone?.Children.First(c => c.SemanticId == "TelephoneNumber") as SemanticLeafNode; + Assert.Equal("second@test.com", email!.Value); + Assert.Equal("222-2222", phoneTelephoneNumber!.Value); + var available = phone?.Children.First(c => c.SemanticId == "AvailableTime") as SemanticBranchNode; + var availableTime = available?.Children.First(c => c.SemanticId == "AvailableTime_de") as SemanticLeafNode; + Assert.Equal("Montag – Freitag 08:00 bis 16:00", availableTime?.Value); + } + + [Fact] + public void EnrichWithData_WhenBranchHasChildArray_ClonesNodesAndSetsLeafValues() + { + var nameplateNode = new SemanticBranchNode("Nameplate", DataType.Object); + var contactInfoNode = new SemanticBranchNode("ContactInformation", DataType.Array); + var phoneNode = new SemanticBranchNode("Phone", DataType.Object); + var phoneNumberLeaf = new SemanticLeafNode("TelephoneNumber", DataType.String, ""); + phoneNode.AddChild(phoneNumberLeaf); + contactInfoNode.AddChild(phoneNode); + nameplateNode.AddChild(contactInfoNode); + + _sut.EnrichWithData(nameplateNode, ProductId); + + var contactInfo = nameplateNode.Children[0] as SemanticBranchNode; + Assert.Equal(2, contactInfo?.Children.Count); + var phone1 = contactInfo?.Children[0] as SemanticBranchNode; + Assert.Single(phone1?.Children!); + var firstNumber = phone1.Children[0] as SemanticLeafNode; + Assert.Equal("+49571 8870", firstNumber.Value); + var phone2 = contactInfo?.Children[1] as SemanticBranchNode; + Assert.Single(phone1.Children); + var secondNumber = phone2?.Children[0] as SemanticLeafNode; + Assert.Equal("+91 7845129532", secondNumber.Value); + } + + [Fact] + public void EnrichWithData_WhenBranchHasDeepChildArray_ExpandsArrayAndSetsLeafValues() + { + var mcadNode = new SemanticBranchNode("MCAD", DataType.Object); + var documentStepNode = new SemanticBranchNode("Document_STEP", DataType.Array); + var documentIdNode = new SemanticBranchNode("DocumentId", DataType.Object); + var documentVersionNode = new SemanticBranchNode("DocumentVersion", DataType.Object); + var statusValueLeaf = new SemanticLeafNode("StatusValue", DataType.String, ""); + documentVersionNode.AddChild(statusValueLeaf); + documentIdNode.AddChild(documentVersionNode); + documentStepNode.AddChild(documentIdNode); + mcadNode.AddChild(documentStepNode); + + _sut.EnrichWithData(mcadNode, ProductId); + + var documentStep = mcadNode.Children[0] as SemanticBranchNode; + var documentId = documentStep.Children[0] as SemanticBranchNode; + Assert.Equal(2, documentId.Children.Count); + var firstVersion = documentId.Children[0] as SemanticBranchNode; + var firstStatus = firstVersion.Children[0] as SemanticLeafNode; + Assert.Equal("StatusValue", firstStatus.SemanticId); + Assert.Equal("Released", firstStatus.Value); + var secondVersion = documentId.Children[1] as SemanticBranchNode; + var secondStatus = secondVersion.Children[0] as SemanticLeafNode; + Assert.Equal("StatusValue", secondStatus.SemanticId); + Assert.Equal("Inprogress", secondStatus.Value); + } + + [Fact] + public void EnrichWithData_ForBranchNodeWithSpectificIndex_WithNestedComplexdata_SetsValueFromData() + { + var rootBranch = new SemanticBranchNode("Document_aastwinengine_00", DataType.Object); + rootBranch.AddChild(new SemanticLeafNode("IsPrimary", DataType.String, "")); + var branch = new SemanticBranchNode("DocumentClassification", DataType.Array); + rootBranch.AddChild(branch); + branch.AddChild(new SemanticLeafNode("ClassId", DataType.String, "")); + branch.AddChild(new SemanticLeafNode("ClassificationSystem", DataType.String, "")); + + _sut.EnrichWithData(rootBranch, ProductId); + + var documents = rootBranch.Children.OfType().ToList(); + Assert.Equal(2, documents.Count); + var primary = rootBranch.Children.First(c => c.SemanticId == "IsPrimary") as SemanticLeafNode; + Assert.Equal("true", primary.Value); + var firstDocumentClassification = documents[0]; + var firstClassId = firstDocumentClassification.Children.First(c => c.SemanticId == "ClassId") as SemanticLeafNode; + var firstClassificationSystem = firstDocumentClassification.Children.First(c => c.SemanticId == "ClassificationSystem") as SemanticLeafNode; + Assert.Equal("02-02", firstClassId.Value); + Assert.Equal("VDI2770:2020", firstClassificationSystem.Value); + var secondDocumentClassification = documents[1]; + var secondClassId = secondDocumentClassification.Children.First(c => c.SemanticId == "ClassId") as SemanticLeafNode; + var secondClassificationSystem = secondDocumentClassification.Children.First(c => c.SemanticId == "ClassificationSystem") as SemanticLeafNode; + Assert.Equal("STEP", secondClassId.Value); + Assert.Equal("IDTA-MCAD:2022", secondClassificationSystem.Value); + } + + [Fact] + public void EnrichWithData_ForBranchNodeWithSpectificIndex_WithoutNestedComplexdata_SetsValueFromData() + { + var rootBranch = new SemanticBranchNode("Document_aastwinengine_01", DataType.Object); + rootBranch.AddChild(new SemanticLeafNode("IsPrimary", DataType.String, "")); + var branch = new SemanticBranchNode("DocumentClassification", DataType.Object); + rootBranch.AddChild(branch); + branch.AddChild(new SemanticLeafNode("ClassId", DataType.String, "")); + branch.AddChild(new SemanticLeafNode("ClassificationSystem", DataType.String, "")); + + _sut.EnrichWithData(rootBranch, ProductId); + + var documents = rootBranch.Children.OfType().ToList(); + Assert.Equal(1, documents.Count); + var primary = rootBranch.Children.First(c => c.SemanticId == "IsPrimary") as SemanticLeafNode; + Assert.Equal("false", primary.Value); + var firstDocumentClassification = documents[0]; + var firstClassId = firstDocumentClassification.Children.First(c => c.SemanticId == "ClassId") as SemanticLeafNode; + var firstClassificationSystem = firstDocumentClassification.Children.First(c => c.SemanticId == "ClassificationSystem") as SemanticLeafNode; + Assert.Equal("01-01", firstClassId.Value); + Assert.Equal("VDI2770:2025", firstClassificationSystem.Value); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/TestData.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/TestData.cs new file mode 100644 index 0000000..8f16093 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.UnitTests/Infrastructure/Providers/TestData.cs @@ -0,0 +1,157 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.UnitTests.Infrastructure.Providers; + +public static class TestData +{ + public const string TestMetaData = @"[ + { + ""globalAssetId"": ""WeatherStation"", + ""idShort"": ""SensorWeatherStationExample"", + ""id"": ""WeatherStation"", + ""specificAssetIds"": [], + ""assetInformationData"": { + ""defaultThumbnail"": { + ""contentType"": ""image/svg+xml"", + ""path"": ""AAS_Logo.svg"" + } + } + }, + { + ""globalAssetId"": ""mm-2206-1631"", + ""idShort"": ""2206-1631/1000-859"", + ""id"": ""mm-2206-1631/1000-859"", + ""specificAssetIds"": [], + ""assetInformationData"": { + ""defaultThumbnail"": { + ""contentType"": ""image/svg+xml"", + ""path"": ""AAS_Logo.svg"" + } + } + }, + { + ""globalAssetId"": ""100-859"", + ""idShort"": """", + ""id"": ""1000-859"", + ""specificAssetIds"": [], + ""assetInformationData"": { + ""defaultThumbnail"": { + ""contentType"": ""image/svg+xml"", + ""path"": ""AAS_Logo.svg"" + } + } + }, + { + ""globalAssetId"": ""m&m-259"", + ""idShort"": """", + ""id"": """", + ""specificAssetIds"": [] + }, + { + ""globalAssetId"": ""SoftwareNameplate"", + ""idShort"": ""SoftwareNameplateAAS"", + ""id"": ""SoftwareNameplate/1/0"", + ""specificAssetIds"": [] + }, + { + ""globalAssetId"": ""ContactInformation"", + ""idShort"": ""ContactInformationAAS"", + ""id"": ""ContactInformation"", + ""specificAssetIds"": [ + { + ""name"": ""serialNumber"", + ""value"": ""SN-859-001"" + } + ], + ""assetInformationData"": { + ""defaultThumbnail"": { + ""contentType"": ""image/svg+xml"", + ""path"": ""AAS_Logo.svg"" + } + } + }, + { + ""globalAssetId"": ""DigitalNameplate"", + ""idShort"": ""DigitalNameplateAAS"", + ""id"": ""DigitalNameplate/3/0"", + ""specificAssetIds"": null + } + ]"; + + public const string TestSubmodelData = @" + { + ""test-submodelId"": { + ""root"": { + ""Email"": ""test@example.com"" + }, + ""Email"": ""test@example.com"", + ""ContactInformation"": { + ""Email"": ""contact@test.com"", + ""Phone"": ""555-1234"" + }, + ""ContactInformations"": { + ""ContactInformation"": [ + { + ""Email"": ""first@test.com"", + ""Phone"": { + ""TelephoneNumber"": ""111-1111"" + } + }, + { + ""Email"": ""second@test.com"", + ""Phone"": { + ""TelephoneNumber"": ""222-2222"", + ""AvailableTime"": { + ""AvailableTime_de"": ""Montag – Freitag 08:00 bis 16:00"" + } + } + } + ] + }, + ""ManufacturerName"": { + ""ManufacturerName_en"": ""M&M"" + }, + ""Document"": [ + { + ""IsPrimary"": ""true"", + ""DocumentClassification"": [ + { + ""ClassId"": ""02-02"", + ""ClassificationSystem"": ""VDI2770:2020"" + }, + { + ""ClassId"": ""STEP"", + ""ClassificationSystem"": ""IDTA-MCAD:2022"" + } + ] + }, + { + ""IsPrimary"": ""false"", + ""DocumentClassification"": { + ""ClassId"": ""01-01"", + ""ClassificationSystem"": ""VDI2770:2025"" + } + } + ], + ""Nameplate"": { + ""ContactInformation"": { + ""Phone"": [ + { + ""TelephoneNumber"": ""+49571 8870"" + }, + { + ""TelephoneNumber"": ""+91 7845129532"" + } + ] + } + }, + ""MCAD"": { + ""Document_STEP"": { + ""DocumentId"": { + ""DocumentVersion"": [ + { + ""StatusValue"": ""Released"" + }, + { + ""StatusValue"": ""Inprogress"" + } + ]}}}}}"; +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/AAS.TwinEngine.Plugin.TestPlugin.csproj b/source/AAS.TwinEngine.Plugin.TestPlugin/AAS.TwinEngine.Plugin.TestPlugin.csproj new file mode 100644 index 0000000..4f16565 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/AAS.TwinEngine.Plugin.TestPlugin.csproj @@ -0,0 +1,38 @@ + + + + net8.0 + enable + enable + Linux + . + 2ba5fa1c-2588-4655-bd1f-bc7cd274eba9 + true + + + + False + + + + False + + + + + + + + + + + + + + + + + + + + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/Handler/IManifestHandler.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/Handler/IManifestHandler.cs new file mode 100644 index 0000000..85f3453 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/Handler/IManifestHandler.cs @@ -0,0 +1,8 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Responses; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Handler; + +public interface IManifestHandler +{ + Task GetManifestData(CancellationToken cancellationToken); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/Handler/ManifestHandler.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/Handler/ManifestHandler.cs new file mode 100644 index 0000000..c9c3496 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/Handler/ManifestHandler.cs @@ -0,0 +1,18 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.MappingProfiles; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Responses; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Manifest; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Handler; + +public class ManifestHandler(ILogger logger, + IManifestService manifestService) : IManifestHandler +{ + public async Task GetManifestData(CancellationToken cancellationToken) + { + logger.LogInformation("Start executing request for manifest data"); + + var manifest = await manifestService.GetManifestData(cancellationToken); + + return manifest.ToDto(); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/ManifestController.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/ManifestController.cs new file mode 100644 index 0000000..30ea6ea --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/ManifestController.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Nodes; + +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Responses; + +using Asp.Versioning; + +using Microsoft.AspNetCore.Mvc; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest; + +[ApiController] +[Route("")] +[ApiVersion(1)] +public class ManifestController(IManifestHandler manifestHandler) :ControllerBase +{ + [HttpGet("manifest")] + [ProducesResponseType(typeof(JsonObject), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ActionResult), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ActionResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ActionResult), StatusCodes.Status500InternalServerError)] + public async Task> RetrieveManifestDataAsync(CancellationToken cancellationToken) + { + var manifestData = await manifestHandler.GetManifestData(cancellationToken); + + return Ok(manifestData); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/MappingProfiles/ManifestMappingProfile.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/MappingProfiles/ManifestMappingProfile.cs new file mode 100644 index 0000000..4074303 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/MappingProfiles/ManifestMappingProfile.cs @@ -0,0 +1,20 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Responses; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Manifest; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.MappingProfiles; + +public static class ManifestMappingProfile +{ + public static ManifestDto ToDto(this ManifestData? data) + { + return new ManifestDto() + { + Capabilities = new CapabilitiesDto() + { + HasAssetInformation = data.Capabilities.HasAssetInformation, + HasShellDescriptor = data.Capabilities.HasShellDescriptor + }, + SupportedSemanticIds = data.SupportedSemanticIds + }; + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/Responses/ManifestDto.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/Responses/ManifestDto.cs new file mode 100644 index 0000000..fda6ff2 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Manifest/Responses/ManifestDto.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Responses; + +public class ManifestDto +{ + [JsonPropertyName("supportedSemanticIds")] + public required IList SupportedSemanticIds { get; init; } + + [JsonPropertyName("capabilities")] + public required CapabilitiesDto Capabilities { get; set; } +} + +public class CapabilitiesDto +{ + [JsonPropertyName("hasShellDescriptor")] + public bool HasShellDescriptor { get; set; } + + [JsonPropertyName("hasAssetInformation")] + public bool HasAssetInformation { get; set; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Handler/IMetaDataHandler.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Handler/IMetaDataHandler.cs new file mode 100644 index 0000000..a9aedf6 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Handler/IMetaDataHandler.cs @@ -0,0 +1,13 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Requests; +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Responses; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Handler; + +public interface IMetaDataHandler +{ + Task GetShellDescriptors(GetShellDescriptorsRequest request, CancellationToken cancellationToken); + + Task GetShellDescriptor(GetShellDescriptorRequest request, CancellationToken cancellationToken); + + Task GetAsset(GetAssetRequest request, CancellationToken cancellationToken); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Handler/MetaDataHandler.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Handler/MetaDataHandler.cs new file mode 100644 index 0000000..349acc3 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Handler/MetaDataHandler.cs @@ -0,0 +1,57 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.MappingProfiles; +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Requests; +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Responses; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.MetaData; +using AAS.TwinEngine.Plugin.TestPlugin.Common.Extensions; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Handler; + +public class MetaDataHandler( + ILogger logger, + IMetaDataService metaDataService) : IMetaDataHandler +{ + public async Task GetShellDescriptors(GetShellDescriptorsRequest request, CancellationToken cancellationToken) + { + request?.Limit.ValidateLimit(logger); + + logger.LogDebug("Start executing get request for shell-descriptors metadata"); + + var shellDescriptors = await metaDataService.GetShellDescriptorsAsync(request?.Limit, request?.Cursor, cancellationToken); + + return shellDescriptors.ToDto(); + } + + public async Task GetShellDescriptor(GetShellDescriptorRequest request, CancellationToken cancellationToken) + { + logger.LogDebug($"Start executing get request for shell-descriptor metadata for {request.aasIdentifier}"); + + var shellDescriptorMetaData = await metaDataService.GetShellDescriptorAsync(request.aasIdentifier, cancellationToken); + + if (shellDescriptorMetaData != null) + { + var response = shellDescriptorMetaData.ToDto(); + return response; + } + + logger.LogWarning($"Shell-descriptor metadata not found for {request.aasIdentifier}."); + throw new NotFoundException(ExceptionMessages.ShellDescriptorDataNotFound); + } + + public async Task GetAsset(GetAssetRequest request, CancellationToken cancellationToken) + { + logger.LogDebug("Start executing get request for asset metadata element"); + + var assetMetaData = await metaDataService.GetAssetAsync(request.shellIdentifier, cancellationToken); + + if (assetMetaData != null) + { + var response = assetMetaData.ToDto(); + return response; + } + + logger.LogWarning($"Asset metadata not found for {request.shellIdentifier}."); + throw new NotFoundException(ExceptionMessages.AssetNotFound); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MappingProfiles/AssetMappingProfile.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MappingProfiles/AssetMappingProfile.cs new file mode 100644 index 0000000..24a79a1 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MappingProfiles/AssetMappingProfile.cs @@ -0,0 +1,27 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Responses; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.MappingProfiles; + +public static class AssetMappingProfile +{ + public static AssetDto ToDto(this AssetData? data) + { + return new AssetDto + { + GlobalAssetId = data.GlobalAssetId, + SpecificAssetIds = data.SpecificAssetIds?.Select(id => new SpecificAssetIdsDto + { + Name = id.Name, + Value = id.Value + }).ToList(), + DefaultThumbnail = data.DefaultThumbnail == null + ? null + : new DefaultThumbnailDto + { + ContentType = data.DefaultThumbnail.ContentType, + Path = data.DefaultThumbnail.Path + } + }; + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MappingProfiles/ShellDescriptorProfile.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MappingProfiles/ShellDescriptorProfile.cs new file mode 100644 index 0000000..8e81ef6 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MappingProfiles/ShellDescriptorProfile.cs @@ -0,0 +1,22 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Responses; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.MappingProfiles; + +public static class ShellDescriptorProfile +{ + public static ShellDescriptorDto ToDto(this ShellDescriptorData data) + => new() + { + GlobalAssetId = data.GlobalAssetId, + IdShort = data.IdShort, + Id = data.Id, + SpecificAssetIds = data.SpecificAssetIds? + .Select(x => new SpecificAssetIdsDto + { + Name = x.Name, + Value = x.Value + }) + .ToList() + }; +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MappingProfiles/ShellDescriptorsProfile.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MappingProfiles/ShellDescriptorsProfile.cs new file mode 100644 index 0000000..34b98a1 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MappingProfiles/ShellDescriptorsProfile.cs @@ -0,0 +1,21 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Responses; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.MappingProfiles; + +public static class ShellDescriptorsProfile +{ + public static ShellDescriptorsDto ToDto(this ShellDescriptorsData descriptors) + { + ArgumentNullException.ThrowIfNull(descriptors); + + return new ShellDescriptorsDto + { + PagingMetaData = new PagingMetaDataDto + { + Cursor = descriptors.PagingMetaData!.Cursor + }, + Result = descriptors.Result?.Select(s => s.ToDto()).ToList() + }; + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MetaDataController.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MetaDataController.cs new file mode 100644 index 0000000..6edd2c3 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/MetaDataController.cs @@ -0,0 +1,63 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Requests; +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Responses; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions.Responses; +using AAS.TwinEngine.Plugin.TestPlugin.Common.Extensions; + +using Asp.Versioning; + +using Microsoft.AspNetCore.Mvc; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData; + +[ApiController] +[Route("metadata")] +[ApiVersion(1)] +public class MetaDataController(IMetaDataHandler metaDataHandler) : ControllerBase +{ + [HttpGet("shells")] + [ProducesResponseType(typeof(ShellDescriptorsDto), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status500InternalServerError)] + public async Task> GetShellDescriptorsAsync([FromQuery] int? limit, [FromQuery] string? cursor, CancellationToken cancellationToken) + { + var request = new GetShellDescriptorsRequest(limit, cursor); + + var response = await metaDataHandler.GetShellDescriptors(request, cancellationToken); + + return Ok(response); + } + + [HttpGet("shells/{AasIdentifier}")] + [ProducesResponseType(typeof(ShellDescriptorDto), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status500InternalServerError)] + public async Task> GetShellDescriptorAsync([FromRoute] string aasIdentifier, CancellationToken cancellationToken) + { + var decodedAasIdentifier = aasIdentifier.DecodeBase64(); + + var request = new GetShellDescriptorRequest(decodedAasIdentifier); + + var response = await metaDataHandler.GetShellDescriptor(request, cancellationToken); + + return Ok(response); + } + + [HttpGet("assets/{shellIdentifier}")] + [ProducesResponseType(typeof(AssetDto), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status500InternalServerError)] + public async Task> GetAssetAsync([FromRoute] string shellIdentifier, CancellationToken cancellationToken) + { + var decodedAasIdentifier = shellIdentifier.DecodeBase64(); + + var request = new GetAssetRequest(decodedAasIdentifier); + + var response = await metaDataHandler.GetAsset(request, cancellationToken); + + return Ok(response); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Requests/GetAssetRequest.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Requests/GetAssetRequest.cs new file mode 100644 index 0000000..74e1ea7 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Requests/GetAssetRequest.cs @@ -0,0 +1,3 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Requests; + +public record GetAssetRequest(string shellIdentifier); diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Requests/GetShellDescriptorRequest.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Requests/GetShellDescriptorRequest.cs new file mode 100644 index 0000000..8f5dc75 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Requests/GetShellDescriptorRequest.cs @@ -0,0 +1,3 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Requests; + +public record GetShellDescriptorRequest(string aasIdentifier); diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Requests/GetShellDescriptorsRequest.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Requests/GetShellDescriptorsRequest.cs new file mode 100644 index 0000000..2e2a976 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Requests/GetShellDescriptorsRequest.cs @@ -0,0 +1,3 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Requests; + +public record GetShellDescriptorsRequest(int? Limit, string? Cursor); diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Responses/AssetDto.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Responses/AssetDto.cs new file mode 100644 index 0000000..1053530 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Responses/AssetDto.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Responses; + +public class AssetDto +{ + [JsonPropertyName("globalAssetId")] + public string? GlobalAssetId { get; set; } + + [JsonPropertyName("specificAssetIds")] + public List? SpecificAssetIds { get; set; } + + [JsonPropertyName("defaultThumbnail")] + public DefaultThumbnailDto? DefaultThumbnail { get; set; } +} + +public class DefaultThumbnailDto +{ + [JsonPropertyName("path")] + public string? Path { get; set; } + + [JsonPropertyName("contentType")] + public string? ContentType { get; set; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Responses/ShellDescriptorDto.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Responses/ShellDescriptorDto.cs new file mode 100644 index 0000000..153bc55 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Responses/ShellDescriptorDto.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Responses; + +public class ShellDescriptorDto +{ + [JsonPropertyName("globalAssetId")] + public string? GlobalAssetId { get; set; } + + [JsonPropertyName("idShort")] + public string? IdShort { get; set; } + + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("specificAssetIds")] + public List? SpecificAssetIds { get; set; } +} + +public class SpecificAssetIdsDto +{ + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("value")] + public string? Value { get; set; } +} + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Responses/ShellDescriptorsDto.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Responses/ShellDescriptorsDto.cs new file mode 100644 index 0000000..404c88f --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/MetaData/Responses/ShellDescriptorsDto.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Responses; + +public class ShellDescriptorsDto +{ + [JsonPropertyName("paging_metadata")] + public PagingMetaDataDto? PagingMetaData { get; set; } + + [JsonPropertyName("result")] + public IList? Result { get; init; } +} + +public class PagingMetaDataDto +{ + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Handler/ISubmodelHandler.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Handler/ISubmodelHandler.cs new file mode 100644 index 0000000..fe0492d --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Handler/ISubmodelHandler.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Nodes; + +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Requests; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Handler; + +public interface ISubmodelHandler +{ + Task GetSubmodelData(GetSubmodelDataRequest request, CancellationToken cancellationToken); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Handler/SubmodelHandler.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Handler/SubmodelHandler.cs new file mode 100644 index 0000000..5f4b562 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Handler/SubmodelHandler.cs @@ -0,0 +1,29 @@ +using System.Text.Json.Nodes; + +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Requests; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Handler; + +public class SubmodelHandler( + ILogger logger, + ISubmodelService submodelService, + IJsonSchemaParser jsonSchemaParser, + ISemanticTreeHandler semanticTreeHandler) : ISubmodelHandler +{ + public async Task GetSubmodelData(GetSubmodelDataRequest request, CancellationToken cancellationToken) + { + logger.LogDebug("Start executing get request for product data"); + + logger.LogInformation("Processing request for submodel ID: {submodelId}", request.submodelId); + + var semanticIds = jsonSchemaParser.ParseJsonSchema(request.dataQuery); + + var filledSemanticIds = await submodelService.GetValuesBySemanticIds(semanticIds, request.submodelId); + + var result = semanticTreeHandler.GetJson(filledSemanticIds, request.dataQuery); + + return result; + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Requests/GetSubmodelDataRequest.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Requests/GetSubmodelDataRequest.cs new file mode 100644 index 0000000..d0c8f94 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Requests/GetSubmodelDataRequest.cs @@ -0,0 +1,5 @@ +using Json.Schema; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Requests; + +public record GetSubmodelDataRequest(string submodelId, JsonSchema dataQuery); diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/IJsonSchemaParser.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/IJsonSchemaParser.cs new file mode 100644 index 0000000..ed6a21e --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/IJsonSchemaParser.cs @@ -0,0 +1,13 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +using Json.Schema; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; + +/// +/// Parses a complex JSON schema and converts it into a semantic tree structure. +/// +public interface IJsonSchemaParser +{ + SemanticTreeNode ParseJsonSchema(JsonSchema jsonSchema); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/IJsonSchemaValidator.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/IJsonSchemaValidator.cs new file mode 100644 index 0000000..da57e05 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/IJsonSchemaValidator.cs @@ -0,0 +1,8 @@ +using Json.Schema; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; + +public interface IJsonSchemaValidator +{ + void ValidateResponseContent(string responseJson, JsonSchema requestSchema); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/ISemanticTreeHandler.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/ISemanticTreeHandler.cs new file mode 100644 index 0000000..515f813 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/ISemanticTreeHandler.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Nodes; + +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +using Json.Schema; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; + +/// +/// Converts a semantic tree node with values into a structured JSON format. +/// +public interface ISemanticTreeHandler +{ + JsonObject GetJson(SemanticTreeNode semanticTreeNodeWithValues, JsonSchema dataQuery); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/JsonSchemaParser.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/JsonSchemaParser.cs new file mode 100644 index 0000000..f346f22 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/JsonSchemaParser.cs @@ -0,0 +1,166 @@ +using System.Text.Json; + +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +using Json.Schema; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; + +public class JsonSchemaParser(ILogger logger) : IJsonSchemaParser +{ + public SemanticTreeNode ParseJsonSchema(JsonSchema jsonSchema) + { + ValidateRequest(jsonSchema); + return CreateSemanticTree(jsonSchema); + } + + private void ValidateRequest(JsonSchema jsonSchema) + { + try + { + var node = JsonSerializer.SerializeToNode(jsonSchema); + var result = MetaSchemas.Draft7.Evaluate(node, new EvaluationOptions { OutputFormat = OutputFormat.List }); + if (!result.IsValid) + { + logger.LogError("Requested schema is not validate"); + throw new BadRequestException(ExceptionMessages.RequestBodyInvalid); + } + } + catch (JsonException) + { + logger.LogError("Requested schema is not validate"); + throw new BadRequestException(ExceptionMessages.FailedParsingJsonSchema); + } + } + + private SemanticTreeNode CreateSemanticTree(JsonSchema jsonSchema) + { + var propertiesKeyword = jsonSchema.GetKeyword(); + if (propertiesKeyword == null || !propertiesKeyword.Properties.Any()) + { + throw new BadRequestException(ExceptionMessages.InvalidJsonSchemaRootElement); + } + + var rootProperty = propertiesKeyword.Properties.First(); + return ProcessProperty(rootProperty.Key, rootProperty.Value, jsonSchema.GetKeyword()); + } + + private SemanticTreeNode ProcessProperty(string schemaPropertyName, JsonSchema property, DefinitionsKeyword definitions) + { + var refKeyword = property.GetKeyword(); + if (refKeyword != null) + { + return HandleReference(schemaPropertyName, property, definitions); + } + + var typeKeyword = property.GetKeyword(); + if (typeKeyword == null) + { + return new SemanticLeafNode(schemaPropertyName, DataType.String, ""); + } + + var schemaType = GetSchemaType(typeKeyword); + if (schemaType is DataType.Object or DataType.Array) + { + return BuildObjectNode(schemaPropertyName, schemaType, property, definitions); + } + + return new SemanticLeafNode(schemaPropertyName, schemaType, ""); + } + + private SemanticTreeNode HandleReference(string schemaPropertyName, JsonSchema property, DefinitionsKeyword definitions) + { + var refKeyword = property.GetKeyword(); + if (refKeyword == null) + { + return new SemanticLeafNode(schemaPropertyName, DataType.String, ""); + } + + var definitionKey = refKeyword.Reference.ToString().Replace("#/definitions/", ""); + if (definitions == null || !definitions.Definitions.TryGetValue(definitionKey, out var def)) + { + return new SemanticLeafNode(schemaPropertyName, DataType.Unknown, ""); + } + + var defTypeKeyword = def.GetKeyword(); + if (defTypeKeyword == null) + { + return new SemanticLeafNode(schemaPropertyName, DataType.String, ""); + } + + var schemaType = GetSchemaType(defTypeKeyword); + if (schemaType is DataType.Object or DataType.Array) + { + return BuildObjectNode(schemaPropertyName, schemaType, def, definitions); + } + + return new SemanticLeafNode(schemaPropertyName, schemaType, ""); + } + + private SemanticBranchNode BuildObjectNode(string schemaPropertyName, DataType dataType, JsonSchema schema, DefinitionsKeyword definitions) + { + var branchNode = new SemanticBranchNode(schemaPropertyName, dataType); + + switch (dataType) + { + case DataType.Object: + { + var propertiesKeyword = schema.GetKeyword(); + if (propertiesKeyword != null) + { + foreach (var prop in propertiesKeyword.Properties) + { + branchNode.AddChild(ProcessProperty(prop.Key, prop.Value, definitions)); + } + } + + break; + } + case DataType.Array: + { + var itemsKeyword = schema.GetKeyword(); + if (itemsKeyword == null) + { + var propertiesKeyword = schema.GetKeyword(); + if (propertiesKeyword != null) + { + foreach (var prop in propertiesKeyword.Properties) + { + branchNode.AddChild(ProcessProperty(prop.Key, prop.Value, definitions)); + } + } + + break; + } + + if (itemsKeyword is { SingleSchema: not null }) + { + branchNode.AddChild(ProcessProperty("item", itemsKeyword.SingleSchema, definitions)); + } + + break; + } + } + + return branchNode; + } + + private static DataType GetSchemaType(TypeKeyword typeKeyword) + { + var t = typeKeyword.Type; + + return t switch + { + _ when t.HasFlag(SchemaValueType.Object) => DataType.Object, + _ when t.HasFlag(SchemaValueType.Array) => DataType.Array, + _ when t.HasFlag(SchemaValueType.String) => DataType.String, + _ when t.HasFlag(SchemaValueType.Integer) => DataType.Integer, + _ when t.HasFlag(SchemaValueType.Number) => DataType.Number, + _ when t.HasFlag(SchemaValueType.Boolean) => DataType.Boolean, + _ => DataType.String + }; + } + +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/JsonSchemaValidator.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/JsonSchemaValidator.cs new file mode 100644 index 0000000..6081bef --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/JsonSchemaValidator.cs @@ -0,0 +1,232 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel.Config; + +using Json.Schema; + +using Microsoft.Extensions.Options; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; + +public class JsonSchemaValidator(IOptions semantics, ILogger logger) : IJsonSchemaValidator +{ + private readonly string _contextPrefix = semantics.Value.IndexContextPrefix; + private const string DefinitionsPrefix = "#/definitions/"; + + private static readonly JsonSerializerOptions Serialization = new() + { + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) } + }; + + public void ValidateResponseContent(string responseJson, JsonSchema requestSchema) + { + if (string.IsNullOrWhiteSpace(responseJson)) + { + LogAndThrowException("Response JSON is empty."); + } + + if (!TryParseJson(responseJson, out var responseDoc, out var parseError)) + { + LogAndThrowException($"Failed to parse response JSON: {parseError}"); + } + + if (!TryNormalizeSchema(requestSchema, out var normalizedSchema, out var normalizeError)) + { + LogAndThrowException($"Failed to normalize request schema: {normalizeError}"); + } + + if (!TryRegisterJsonSchema(normalizedSchema, out var registerError)) + { + LogAndThrowException($"Failed to register schema: {registerError}"); + } + + try + { + var schema = JsonSchema.FromText(normalizedSchema.ToJsonString()); + var result = schema.Evaluate(responseDoc!.RootElement, new EvaluationOptions { OutputFormat = OutputFormat.List }); + if (!result.IsValid) + { + LogAndThrowException("Response did not validate against schema."); + } + } + catch (Exception ex) + { + LogAndThrowException("Exception occurred during response validation.", ex); + } + } + + private void LogAndThrowException(string logMessage, Exception? ex = null) + { + if (ex != null) + { + logger.LogError(ex, logMessage); + } + else + { + logger.LogError(logMessage); + } + + throw new NotFoundException(ExceptionMessages.ResourceNotValid); + } + + private static bool TryParseJson(string json, out JsonDocument? document, out string? error) + { + error = null; + document = null; + + try + { + document = JsonDocument.Parse(json); + return true; + } + catch (Exception ex) + { + error = $"JSON parsing failed: {ex.Message}"; + return false; + } + } + + private bool TryNormalizeSchema(JsonSchema schema, out JsonObject normalized, out string? error) + { + error = null; + normalized = []; + + try + { + var json = JsonSerializer.Serialize(schema, Serialization); + + normalized = JsonNode.Parse(json)?.AsObject() + ?? throw new ArgumentException("Failed to parse schema JSON."); + + EscapeJsonReferencePointers(normalized); + normalized["$id"] = normalized["$id"]?.GetValue() ?? $"urn:uuid:{Guid.NewGuid():D}"; + + return true; + } + catch (Exception ex) + { + error = $"Schema normalization failed: {ex.Message}"; + return false; + } + } + + private static bool TryRegisterJsonSchema(JsonObject schemaJsonObject, out string? registrationErrorMessage) + { + registrationErrorMessage = null; + + try + { + var jsonSchema = JsonSchema.FromText(schemaJsonObject.ToJsonString()); + var schemaIdentifierUri = new Uri(schemaJsonObject["$id"]!.GetValue()!); + SchemaRegistry.Global.Register(schemaIdentifierUri, jsonSchema); + return true; + } + catch (Exception exception) + { + registrationErrorMessage = $"Schema registration failed: {exception.Message}"; + return false; + } + } + + private void EscapeJsonReferencePointers(JsonNode? currentNode) + { + switch (currentNode) + { + case JsonObject jsonObjectNode: + ProcessJsonObjectForEscaping(jsonObjectNode); + break; + + case JsonArray jsonArrayNode: + foreach (var arrayElement in jsonArrayNode) + { + EscapeJsonReferencePointers(arrayElement); + } + + break; + } + } + + private void ProcessJsonObjectForEscaping(JsonObject jsonObject) + { + var propertiesToRename = jsonObject + .Select(property => property.Key) + .Select(propertyName => (originalName: propertyName, strippedName: RemoveContextSuffix(propertyName))) + .Where(namePair => namePair.strippedName != namePair.originalName) + .ToList(); + + foreach (var (originalName, strippedName) in propertiesToRename) + { + RenameJsonProperty(jsonObject, originalName, strippedName); + } + + if (jsonObject.TryGetPropertyValue("required", out var requiredPropertiesNode) && + requiredPropertiesNode is JsonArray requiredPropertiesArray) + { + RemoveContextSuffixFromRequiredProperties(requiredPropertiesArray); + } + + foreach (var property in jsonObject.ToList()) + { + var propertyName = property.Key; + var propertyValue = property.Value; + + if (propertyName == "$ref" && + propertyValue is JsonValue referenceValue && + referenceValue.TryGetValue(out var referenceString) && + referenceString.StartsWith(DefinitionsPrefix, StringComparison.OrdinalIgnoreCase)) + { + jsonObject["$ref"] = BuildEscapedReferencePath(referenceString); + } + else + { + EscapeJsonReferencePointers(propertyValue); + } + } + } + + private void RemoveContextSuffixFromRequiredProperties(JsonArray requiredProperties) + { + for (var index = 0; index < requiredProperties.Count; index++) + { + if (requiredProperties[index]?.GetValue() is { } propertyName) + { + requiredProperties[index] = RemoveContextSuffix(propertyName); + } + } + } + + private string BuildEscapedReferencePath(string originalReferencePath) + { + var referenceWithoutPrefix = originalReferencePath[DefinitionsPrefix.Length..]; + + var strippedReference = RemoveContextSuffix(referenceWithoutPrefix); + + var escapedReference = strippedReference.Replace("~", "~0", StringComparison.OrdinalIgnoreCase).Replace("/", "~1", StringComparison.OrdinalIgnoreCase); + + return DefinitionsPrefix + escapedReference; + } + + private string RemoveContextSuffix(string propertyName) + { + var suffixIndex = propertyName.IndexOf(_contextPrefix, StringComparison.Ordinal); + return suffixIndex >= 0 ? propertyName[..suffixIndex] : propertyName; + } + + private static void RenameJsonProperty(JsonObject jsonObject, string oldPropertyName, string newPropertyName) + { + if (oldPropertyName == newPropertyName) + { + return; + } + + var propertyValue = jsonObject[oldPropertyName]; + jsonObject.Remove(oldPropertyName); + jsonObject[newPropertyName] = propertyValue!; + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/SemanticTreeHandler.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/SemanticTreeHandler.cs new file mode 100644 index 0000000..eff9d9a --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/Services/SemanticTreeHandler.cs @@ -0,0 +1,165 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Nodes; + +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +using Json.Schema; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; + +public class SemanticTreeHandler(IJsonSchemaValidator jsonSchemaValidator) : ISemanticTreeHandler +{ + public JsonObject GetJson(SemanticTreeNode semanticTreeNodeWithValues, JsonSchema dataQuery) + { + var nodeObject = ConvertNode(semanticTreeNodeWithValues); + + var convertedJsonObject = new JsonObject { [semanticTreeNodeWithValues.SemanticId] = nodeObject }; + + var convertedJsonString = JsonSerializer.Serialize(convertedJsonObject); + + jsonSchemaValidator.ValidateResponseContent(convertedJsonString, dataQuery); + + return convertedJsonObject; + } + + private static JsonNode ConvertNode(SemanticTreeNode treeNode) + { + return treeNode switch + { + SemanticLeafNode leaf => ConvertLeafValue(leaf), + SemanticBranchNode branch => ConvertBranchNode(branch), + _ => throw new ArgumentException(ExceptionMessages.UnknownTypeError), + }; + } + + private static JsonNode ConvertLeafValue(SemanticLeafNode leafNode) + { + return leafNode.DataType switch + { + DataType.Boolean => TryParseBoolean(leafNode.Value), + DataType.Integer => TryParseInteger(leafNode.Value), + DataType.Number => TryParseNumber(leafNode.Value), + DataType.String => JsonValue.Create(leafNode.Value), + _ => JsonValue.Create(leafNode.Value) + }; + } + + private static JsonNode TryParseBoolean(string text) + { + return bool.TryParse(text, out var result) + ? JsonValue.Create(result) + : JsonValue.Create(text); + } + + private static JsonNode TryParseInteger(string text) + { + return int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) + ? JsonValue.Create(result) + : JsonValue.Create(text); + } + + private static JsonNode TryParseNumber(string text) + { + return double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) + ? JsonValue.Create(result) + : JsonValue.Create(text); + } + + private static JsonNode ConvertBranchNode(SemanticBranchNode branchNode) + { + var isArray = branchNode.DataType == DataType.Array; + var allBranchNodes = branchNode.Children.All(child => child is SemanticBranchNode); + var allLeafNodes = branchNode.Children.All(child => child is SemanticLeafNode); + var sameSemanticId = branchNode.Children.Select(child => child.SemanticId).Distinct().Count() == 1; + var sameSematicIdAsBranch = branchNode.Children.All(child => child is SemanticLeafNode) && branchNode.Children.All(child => child.SemanticId == branchNode.SemanticId); + var singleNode = branchNode.Children.Count() == 1; + + if (isArray) + { + var elementArray = new JsonArray(); + + if (allBranchNodes && sameSemanticId && !singleNode) + { + foreach (var childBranch in branchNode.Children.Cast()) + { + elementArray.Add(ConvertNode(childBranch)); + } + + return elementArray; + } + + if (allLeafNodes && sameSemanticId && !singleNode) + { + foreach (var leaf in branchNode.Children.Cast()) + { + elementArray.Add(BuildObjectFromLeafNode(leaf)); + } + + return elementArray; + } + + var singleObject = BuildObjectFromChildren(branchNode.Children); + elementArray.Add(singleObject); + return elementArray; + } + + return BuildObjectFromChildren(branchNode.Children); + } + + private static JsonObject BuildObjectFromLeafNode(SemanticLeafNode leafNode) + { + var jsonObject = new JsonObject + { + [leafNode.SemanticId] = ConvertLeafValue(leafNode) + }; + return jsonObject; + } + + private static JsonObject BuildObjectFromChildren(IEnumerable childNode) + { + var jsonObject = new JsonObject(); + + var groups = childNode.GroupBy(child => child.SemanticId); + + foreach (var group in groups) + { + var convertedValues = group.Select(ConvertNode).ToList(); + var count = convertedValues.Count; + var allArrays = convertedValues.All(v => v is JsonArray); + var singleValue = count == 1; + + if (singleValue) + { + jsonObject[group.Key] = convertedValues[0]; + } + else if (allArrays) + { + var mergedArray = new JsonArray(); + foreach (var array in convertedValues.Cast()) + { + foreach (var element in array) + { + mergedArray.Add(element.DeepClone()); + } + } + + jsonObject[group.Key] = mergedArray; + } + else + { + var wrapperArray = new JsonArray(); + foreach (var valueNode in convertedValues) + { + wrapperArray.Add(valueNode.DeepClone()); + } + + jsonObject[group.Key] = wrapperArray; + } + } + + return jsonObject; + } +} + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/SubmodelController.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/SubmodelController.cs new file mode 100644 index 0000000..57bc418 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Api/Submodel/SubmodelController.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Nodes; + +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Requests; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; +using AAS.TwinEngine.Plugin.TestPlugin.Common.Extensions; + +using Asp.Versioning; + +using Json.Schema; + +using Microsoft.AspNetCore.Mvc; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel; + +[ApiController] +[Route("")] +[ApiVersion(1)] +public class SubmodelController(ISubmodelHandler submodelHandler) : ControllerBase +{ + [HttpPost("data/{submodelId}")] + [ProducesResponseType(typeof(JsonObject), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ActionResult), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ActionResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ActionResult), StatusCodes.Status500InternalServerError)] + public async Task> RetrieveDataAsync([FromBody] JsonSchema? dataQuery, [FromRoute] string submodelId, CancellationToken cancellationToken) + { + var decodedSubmodelId = submodelId.DecodeBase64(); + if (dataQuery is null) + { + return BadRequest(ExceptionMessages.InvalidRequestPayload); + } + + var request = new GetSubmodelDataRequest(decodedSubmodelId, dataQuery); + + var aasData = await submodelHandler.GetSubmodelData(request, cancellationToken); + + return Ok(aasData); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Constants/ExceptionMessages.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Constants/ExceptionMessages.cs new file mode 100644 index 0000000..23acbdf --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Constants/ExceptionMessages.cs @@ -0,0 +1,16 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; + +public static class ExceptionMessages +{ + public const string InvalidRequestedLimit = "Limit must be greater than or equal to 1."; + public const string InvalidRequestPayload = "The request payload is invalid."; + public const string RequestBodyInvalid = "The request body could not be processed. Verify the structure and try again."; + public const string FailedParsingJsonSchema = "Failed to parse JSON schema."; + public const string InvalidJsonSchemaRootElement = "The JSON schema must contain a root element."; + public const string UnknownTypeError = "Unknown node type."; + public const string ResourceNotFound = "Required resource not found"; + public const string ResourceNotValid = "Required resource is not valid"; + public const string ResponseIsNotValidate = "Response body is not valid"; + public const string ShellDescriptorDataNotFound = "Required shell information not found"; + public const string AssetNotFound = "Required asset information not found"; +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/BadRequestException.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/BadRequestException.cs new file mode 100644 index 0000000..fd4e505 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/BadRequestException.cs @@ -0,0 +1,33 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; + +public class BadRequestException : Exception +{ + public BadRequestException() + { + } + + public BadRequestException(string message) + : base(message) + { + } + + public BadRequestException(int? errorCode, string message) + : base(message) + { + ErrorCode = errorCode; + } + + public BadRequestException(string message, Exception innerException) + : base(message, innerException) + { + } + + public BadRequestException(string message, string title) + : base(message) + { + Title = title; + } + + public string? Title { get; } + public int? ErrorCode { get; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/CustomException.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/CustomException.cs new file mode 100644 index 0000000..6038d0c --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/CustomException.cs @@ -0,0 +1,33 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; + +public class CustomException : Exception +{ + public CustomException() + { + } + + public CustomException(string message) + : base(message) + { + } + + public CustomException(int? errorCode, string message) + : base(message) + { + ErrorCode = errorCode; + } + + public CustomException(string message, Exception innerException) + : base(message, innerException) + { + } + + public CustomException(string message, string title) + : base(message) + { + Title = title; + } + + public string? Title { get; } + public int? ErrorCode { get; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/ForbiddenException.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/ForbiddenException.cs new file mode 100644 index 0000000..cccdc8a --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/ForbiddenException.cs @@ -0,0 +1,26 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; + +public class ForbiddenException : Exception +{ + public ForbiddenException() + { + } + + public ForbiddenException(string message) + : base(message) + { + } + + public ForbiddenException(string message, Exception innerException) + : base(message, innerException) + { + } + + public ForbiddenException(string message, string title) + : base(message) + { + Title = title; + } + + public string? Title { get; } +} \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/GlobalExceptionHandler.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/GlobalExceptionHandler.cs new file mode 100644 index 0000000..6366921 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/GlobalExceptionHandler.cs @@ -0,0 +1,39 @@ +using System.Net; + +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions.Responses; + +using Microsoft.AspNetCore.Diagnostics; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; + +public class GlobalExceptionHandler(ILogger logger) : IExceptionHandler +{ + public async ValueTask TryHandleAsync(HttpContext httpContext, + Exception exception, + CancellationToken cancellationToken) + { + logger.LogError(exception, "An unhandled exception occurred."); + + var statusCode = exception switch + { + BadRequestException => StatusCodes.Status400BadRequest, + ForbiddenException => StatusCodes.Status403Forbidden, + NotFoundException => StatusCodes.Status404NotFound, + UnauthorizedAccessException => StatusCodes.Status401Unauthorized, + TimeoutException => StatusCodes.Status408RequestTimeout, + _ => StatusCodes.Status500InternalServerError + }; + + var traceId = httpContext.TraceIdentifier; + + var response = new ServiceErrorResponse().Create((HttpStatusCode)statusCode, + title: exception.Message, + traceId: traceId); + + httpContext.Response.ContentType = "application/json"; + httpContext.Response.StatusCode = statusCode; + await httpContext.Response.WriteAsJsonAsync(response, cancellationToken).ConfigureAwait(false); + + return true; + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/InternalServerException.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/InternalServerException.cs new file mode 100644 index 0000000..eeb8447 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/InternalServerException.cs @@ -0,0 +1,26 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; + +public class InternalServerException : Exception +{ + public InternalServerException(string message) + : base(message) + { + } + + public InternalServerException(string message, Exception innerException) + : base(message, innerException) + { + } + + public InternalServerException() + { + } + + public InternalServerException(string message, string title) + : base(message) + { + Title = title; + } + + public string? Title { get; } +} \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/NotFoundException.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/NotFoundException.cs new file mode 100644 index 0000000..0cc4837 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/NotFoundException.cs @@ -0,0 +1,26 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; + +public class NotFoundException : Exception +{ + public NotFoundException() + { + } + + public NotFoundException(string message) + : base(message) + { + } + + public NotFoundException(string message, Exception innerException) + : base(message, innerException) + { + } + + public NotFoundException(string message, string title) + : base(message) + { + Title = title; + } + + public string? Title { get; } +} \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/Responses/ServiceErrorResponse.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/Responses/ServiceErrorResponse.cs new file mode 100644 index 0000000..a33edba --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/Responses/ServiceErrorResponse.cs @@ -0,0 +1,40 @@ +using System.Collections.ObjectModel; +using System.Net; +using System.Text.Json.Serialization; + +using Microsoft.AspNetCore.Mvc; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions.Responses; + +public class ServiceErrorResponse : ProblemDetails +{ + [JsonPropertyName("errors")] + public ReadOnlyCollection? Errors { get; set; } + + [JsonPropertyName("traceId")] + public string? TraceId { get; set; } + + public ServiceErrorResponse Create(HttpStatusCode status, string title, string? traceId = null) + { + var problem = new ServiceErrorResponse + { + Type = "https://tools.ietf.org/html/rfc9110#section-15.5.1", + Title = "An error occurred.", + Status = (int)status, + TraceId = traceId, + Errors = new ReadOnlyCollection(new List + { + new() { Description = title } + }) + }; + + return problem; + } +} + +public class ApiError +{ + [JsonPropertyName("description")] + public string Description { get; set; } = null!; +} + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/UnauthorizedAccessException.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/UnauthorizedAccessException.cs new file mode 100644 index 0000000..92f8996 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Exceptions/UnauthorizedAccessException.cs @@ -0,0 +1,23 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; + +public class UnauthorizedAccessException : Exception +{ + public UnauthorizedAccessException(string message) + : base(message) + { + } + + public UnauthorizedAccessException(string message, Exception innerException) + : base(message, innerException) + { + } + + public UnauthorizedAccessException(string name, string identifier) + : this($"You are not Authorize to access {name} with this identifier {identifier}") + { + } + + public UnauthorizedAccessException() + { + } +} \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Manifest/IManifestProvider.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Manifest/IManifestProvider.cs new file mode 100644 index 0000000..991ca1c --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Manifest/IManifestProvider.cs @@ -0,0 +1,8 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Manifest; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Manifest; + +public interface IManifestProvider +{ + public ManifestData GetManifestData(); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Manifest/IManifestService.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Manifest/IManifestService.cs new file mode 100644 index 0000000..e8d4d8a --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Manifest/IManifestService.cs @@ -0,0 +1,8 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Manifest; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Manifest; + +public interface IManifestService +{ + public Task GetManifestData(CancellationToken cancellationToken); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Manifest/ManifestService.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Manifest/ManifestService.cs new file mode 100644 index 0000000..f283e7c --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Manifest/ManifestService.cs @@ -0,0 +1,8 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Manifest; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Manifest; + +public class ManifestService(IManifestProvider manifestProvider) : IManifestService +{ + public Task GetManifestData(CancellationToken cancellationToken) => Task.FromResult(manifestProvider.GetManifestData()); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/MetaData/IMetaDataProvider.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/MetaData/IMetaDataProvider.cs new file mode 100644 index 0000000..66b9d24 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/MetaData/IMetaDataProvider.cs @@ -0,0 +1,12 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.MetaData; + +public interface IMetaDataProvider +{ + Task GetShellDescriptorsAsync(int? limit, string? cursor, CancellationToken cancellationToken); + + Task GetShellDescriptorAsync(string aasIdentifier, CancellationToken cancellationToken); + + Task GetAssetAsync(string assetIdentifier, CancellationToken cancellationToken); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/MetaData/IMetaDataService.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/MetaData/IMetaDataService.cs new file mode 100644 index 0000000..c49d12e --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/MetaData/IMetaDataService.cs @@ -0,0 +1,12 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.MetaData; + +public interface IMetaDataService +{ + Task GetShellDescriptorsAsync(int? limit, string? cursor, CancellationToken cancellationToken); + + Task GetShellDescriptorAsync(string aasIdentifier, CancellationToken cancellationToken); + + Task GetAssetAsync(string assetIdentifier, CancellationToken cancellationToken); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/MetaData/MetaDataService.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/MetaData/MetaDataService.cs new file mode 100644 index 0000000..956de7e --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/MetaData/MetaDataService.cs @@ -0,0 +1,14 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.MetaData; + +public class MetaDataService( + ILogger logger, + IMetaDataProvider metaDataProvider) : IMetaDataService +{ + public async Task GetShellDescriptorsAsync(int? limit, string? cursor, CancellationToken cancellationToken) => await metaDataProvider.GetShellDescriptorsAsync(limit, cursor, cancellationToken); + + public async Task GetShellDescriptorAsync(string aasIdentifier, CancellationToken cancellationToken) => await metaDataProvider.GetShellDescriptorAsync(aasIdentifier, cancellationToken); + + public async Task GetAssetAsync(string assetIdentifier, CancellationToken cancellationToken) => await metaDataProvider.GetAssetAsync(assetIdentifier, cancellationToken); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/Config/Semantics.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/Config/Semantics.cs new file mode 100644 index 0000000..0cc721b --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/Config/Semantics.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel.Config; + +public class Semantics +{ + public const string Section = "Semantics"; + + [Required] + public string IndexContextPrefix { get; set; } +} + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/ISubmodelProvider.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/ISubmodelProvider.cs new file mode 100644 index 0000000..a4f4d27 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/ISubmodelProvider.cs @@ -0,0 +1,11 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel; + +/// +/// Provides functionality to enrich a semantic tree node with data. +/// +public interface ISubmodelProvider +{ + public SemanticTreeNode EnrichWithData(SemanticTreeNode semanticTreeNode, string submodelId); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/ISubmodelService.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/ISubmodelService.cs new file mode 100644 index 0000000..6e3bc7f --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/ISubmodelService.cs @@ -0,0 +1,11 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel; + +/// +/// Retrieves product data based on a complex data query and returns it in a structured format. +/// +public interface ISubmodelService +{ + public Task GetValuesBySemanticIds(SemanticTreeNode semanticIds, string submodelId); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/SubmodelService.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/SubmodelService.cs new file mode 100644 index 0000000..4fd3b29 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ApplicationLogic/Services/Submodel/SubmodelService.cs @@ -0,0 +1,10 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel; + +public class SubmodelService( + ISubmodelProvider submodelProvider + ) : ISubmodelService +{ + public Task GetValuesBySemanticIds(SemanticTreeNode semanticIds, string submodelId) => Task.FromResult(submodelProvider.EnrichWithData(semanticIds, submodelId)); +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Authorization/.gitkeep b/source/AAS.TwinEngine.Plugin.TestPlugin/Authorization/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Common/Extensions/PaginationValidationExtensions.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Common/Extensions/PaginationValidationExtensions.cs new file mode 100644 index 0000000..6438532 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Common/Extensions/PaginationValidationExtensions.cs @@ -0,0 +1,18 @@ +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Common.Extensions; + +public static class PaginationValidationExtensions +{ + public static void ValidateLimit(this int? limit, ILogger? logger = null) + { + if (limit is null or > 0) + { + return; + } + + logger?.LogError("Invalid pagination limit provided: {Limit}", limit); + throw new BadRequestException(ExceptionMessages.InvalidRequestedLimit); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Common/Extensions/StringExtensions.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Common/Extensions/StringExtensions.cs new file mode 100644 index 0000000..c76b3c0 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Common/Extensions/StringExtensions.cs @@ -0,0 +1,32 @@ +using System.Text; + +using Microsoft.AspNetCore.WebUtilities; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Common.Extensions; + +public static class StringExtensions +{ + public static string DecodeBase64(this string encodedValue) + { + if (string.IsNullOrEmpty(encodedValue)) + throw new ArgumentException("Input string cannot be null or empty.", nameof(encodedValue)); + + try + { + return Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(encodedValue)); + } + catch (FormatException) + { + throw new ArgumentException("The provided string is not a valid Base64 encoded string.", nameof(encodedValue)); + } + } + + public static string EncodeToBase64(this string plainValue) + { + if (string.IsNullOrEmpty(plainValue)) + throw new ArgumentException("Input string cannot be null or empty.", nameof(plainValue)); + + var bytes = System.Text.Encoding.UTF8.GetBytes(plainValue); + return Convert.ToBase64String(bytes); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Data/mock-metadata.json b/source/AAS.TwinEngine.Plugin.TestPlugin/Data/mock-metadata.json new file mode 100644 index 0000000..4d15e7b --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Data/mock-metadata.json @@ -0,0 +1,38 @@ +[ + { + "globalAssetId": "https://mm-software.com/ids/assets/000-001", + "idShort": "M&M01", + "id": "https://mm-software.com/ids/aas/000-001", + "specificAssetIds": [], + "assetInformationData": { + "defaultThumbnail": { + "contentType": "image/jpeg", + "path": "https://cdn.pixabay.com/photo/2023/06/06/16/10/computer-8045026_1280.jpg" + } + } + }, + { + "globalAssetId": "https://mm-software.com/ids/assets/000-002", + "idShort": "M&M02", + "id": "https://mm-software.com/ids/aas/000-002", + "specificAssetIds": [], + "assetInformationData": { + "defaultThumbnail": { + "contentType": "image/jpeg", + "path": "https://cdn.pixabay.com/photo/2023/07/08/07/25/ai-generated-8113887_1280.jpg" + } + } + }, + { + "globalAssetId": "https://mm-software.com/ids/assets/000-003", + "idShort": "M&M03", + "id": "https://mm-software.com/ids/aas/000-003", + "specificAssetIds": [], + "assetInformationData": { + "defaultThumbnail": { + "contentType": "image/jpeg", + "path": "https://images.pexels.com/photos/27934787/pexels-photo-27934787.jpeg" + } + } + } +] diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Data/mock-submodel-data.json b/source/AAS.TwinEngine.Plugin.TestPlugin/Data/mock-submodel-data.json new file mode 100644 index 0000000..b0cb97b --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Data/mock-submodel-data.json @@ -0,0 +1,648 @@ +{ + "https://mm-software.com/submodel/000-001/ContactInformation": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation": [ + { + "0173-1#02-AAO204#003": "Manager001", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Acme Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Marketing" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "234-567-8901" + }, + "0173-1#02-AAO137#003": "Office", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + }, + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "345-678-9012" + }, + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Montags, 10-16 Uhr" + }, + "0173-1#02-AAO137#003": "Mobile" + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "john.doe@example.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Chat", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "8:00 AM to 6:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Bahnhofstraße 25" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "80335" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Bayern" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "John Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/more-info" + }, + { + "0173-1#02-AAO204#003": "Marketing Manager", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Acme Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Marketing" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "234-567-8901" + }, + "0173-1#02-AAO137#003": "Office", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + }, + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "345-678-9012" + }, + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Montags, 10-16 Uhr" + }, + "0173-1#02-AAO137#003": "Mobile" + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "john.doe@example.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Chat", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "8:00 AM to 6:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Bahnhofstraße 25" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "80335" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Bayern" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "John Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/more-info" + } + ] + } + }, + "https://mm-software.com/submodel/000-001/Nameplate": { + "https://admin-shell.io/idta/nameplate/3/0/Nameplate": { + "0112/2///61987#ABN590#002": "https://mm-software.com/Model-000/Serial-Nr-001", + "0112/2///61987#ABA565#009": { + "0112/2///61987#ABA565#009_de": "M&M" + }, + "0112/2///61987#ABP464#002": { + "0112/2///61987#ABP464#002_en": "M&MType ABC" + }, + "0112/2///61987#ABA581#007": "MM11-ABC22-001", + "0112/2///61987#ABB757#007": "2025-01-01", + "0112/2///61987#ABA567#009": { + "0112/2///61987#ABA567#009_en": "\u0022ABC-123\u0022" + }, + "0112/2///61987#ABA950#008": "FMABC1234", + "0112/2///61987#ABA951#009": "12345678", + "0112/2///61987#ABP000#002": "2022", + "0112/2///61360_7#AAS006#001": [ + { + "0112/2///61360_7#AAS009#001": [ + { + "0112/2///61987#ABA231#009": "0173-1#07-DAA603#004", + "0112/2///61987#ABH783#003": "KEMA99IECEX1105/128", + "0112/2///61987#ABO097#001": "2022-01-01", + "0112/2///61987#ABH830#002": "2022-01-01", + "0112/2///61987#ABO100#002": "file", + "0112/2///61987#ABB146#007": "0044" + } + ] + } + ], + "0112/2///61987#ABP462#001": "DE", + "0112/2///61987#ABP463#001": "mm-logo", + "0173-1#02-ABI218#003/0173-1#01-AGZ672#004": [ + { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP_en": "M&MTestsample" + }, + "https://mm-software.com/AssetSpecificProperties/ArbitraryFile": "sampleFile", + "0173-1#02-ABI219#003/0173-1#01-AHD205#004": [ + { + "0173-1#01-AHD205#004": [ + { + "0173-1#02-AAO856#002": "", + "https://admin-shell.io/SMT/General/ArbitraryProp": "", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryFile": "TestFile", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP_en": "\u0022sample\u0022" + } + } + ] + } + ] + } + ] + } + }, + "https://mm-software.com/submodel/000-001/Reliability": { + "https://admin-shell.io/idta/iec62683/1/0/Reliability": { + "0112/2///62683#ACE006#001": "4", + "0112/2///62683#ACG071#001": [ + { + "0112/2///61987#ABA969#007": "DC", + "0112/2///61987#ABA588#004": "14.8", + "0112/2///61987#ABD461#004": "3.8", + "0112/2///61987#ABD462#004": "6.7", + "https://admin-shell.io/idta/FunctionalSafety/RatedOperationalCurrent/1/0": "78.9", + "0112/2///62683#ACE053#001": "TestType001", + "0112/2///62683#ACE070#001": "true", + "0112/2///62683#ACE055#001": "150", + "0112/2///62683#ACE054#001": "45" + } + ], + "0112/2///62683#ACG080#001": [ + { + "0112/2///62683#ACE061#001": "2", + "0112/2///62683#ACE062#001": "7", + "https://admin-shell.io/idta/Reliability/B10/1/0": "1" + } + ] + } + }, + "https://mm-software.com/submodel/000-002/ContactInformation": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation": [ + { + "0173-1#02-AAO204#003": "Manager001", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Acme Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Marketing" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "234-567-8901" + }, + "0173-1#02-AAO137#003": "Office", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + }, + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "345-678-9012" + }, + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Montags, 10-16 Uhr" + }, + "0173-1#02-AAO137#003": "Mobile" + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "john.doe@example.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Chat", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "8:00 AM to 6:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Bahnhofstraße 25" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "80335" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Bayern" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "John Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/more-info" + }, + { + "0173-1#02-AAO204#003": "Assistant Manager", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Beta Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Vertrieb" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "123-456-7890" + }, + "0173-1#02-AAO137#003": "assistant", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "jane.doe@betacorp.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info2", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Email", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "9:00 AM to 5:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Ringstraße 2" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "50667" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Nordrhein-Westfalen" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "Jane Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/info2" + } + ] + } + }, + "https://mm-software.com/submodel/000-002/Nameplate": { + "https://admin-shell.io/idta/nameplate/3/0/Nameplate": { + "0112/2///61987#ABA567#009": { + "0112/2///61987#ABA567#009_en": "\u0022ABC-123\u0022" + }, + "0112/2///61987#ABN590#002": "https://mm-software.com/Model-000/Serial-Nr-002", + "0112/2///61987#ABA565#009": { + "0112/2///61987#ABA565#009_de": "M&M" + }, + "0112/2///61987#ABP464#002": { + "0112/2///61987#ABP464#002_en": "M&MType ABC002" + }, + "0112/2///61987#ABA581#007": "MM11-ABC22-002", + "0112/2///61987#ABB757#007": "2024-01-01", + "0112/2///61987#ABA950#008": "FMABC1234", + "0112/2///61987#ABA951#009": "12345678", + "0112/2///61987#ABP000#002": "2022", + "0112/2///61360_7#AAS006#001": [ + { + "0112/2///61360_7#AAS009#001": [ + { + "0112/2///61987#ABA231#009": "0173-1#07-DAA603#004", + "0112/2///61987#ABH783#003": "KEMA99IECEX1105/128", + "0112/2///61987#ABO097#001": "2022-01-01", + "0112/2///61987#ABH830#002": "2022-01-01", + "0112/2///61987#ABO100#002": "file", + "0112/2///61987#ABB146#007": "0044" + } + ] + } + ], + "0112/2///61987#ABP462#001": "DE", + "0112/2///61987#ABP463#001": "mm-logo", + "0173-1#02-ABI218#003/0173-1#01-AGZ672#004": [ + { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP_en": "M&MTestsample" + }, + "https://mm-software.com/AssetSpecificProperties/ArbitraryFile": "sampleFile", + "0173-1#02-ABI219#003/0173-1#01-AHD205#004": [ + { + "0173-1#01-AHD205#004": [ + { + "0173-1#02-AAO856#002": "", + "https://admin-shell.io/SMT/General/ArbitraryProp": "", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryFile": "TestFile", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP_en": "\u0022sample\u0022" + } + } + ] + } + ] + } + ] + } + }, + "https://mm-software.com/submodel/000-002/Reliability": { + "https://admin-shell.io/idta/iec62683/1/0/Reliability": { + "0112/2///62683#ACE006#001": "4", + "0112/2///62683#ACG071#001": [ + { + "0112/2///61987#ABA969#007": "DC", + "0112/2///61987#ABA588#004": "14.8", + "0112/2///61987#ABD461#004": "3.8", + "0112/2///61987#ABD462#004": "6.7", + "https://admin-shell.io/idta/FunctionalSafety/RatedOperationalCurrent/1/0": "78.9", + "0112/2///62683#ACE053#001": "TestType002", + "0112/2///62683#ACE070#001": "true", + "0112/2///62683#ACE055#001": "150", + "0112/2///62683#ACE054#001": "45" + } + ], + "0112/2///62683#ACG080#001": [ + { + "0112/2///62683#ACE061#001": "2", + "0112/2///62683#ACE062#001": "7", + "https://admin-shell.io/idta/Reliability/B10/1/0": "1" + } + ] + } + }, + "https://mm-software.com/submodel/000-003/ContactInformation": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation": [ + { + "0173-1#02-AAO204#003": "Product Manager0003", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Acme Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Marketing" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "234-567-8901" + }, + "0173-1#02-AAO137#003": "Office", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + }, + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "345-678-9012" + }, + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Montags, 10-16 Uhr" + }, + "0173-1#02-AAO137#003": "Mobile" + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "john.doe@example.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Chat", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "8:00 AM to 6:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Bahnhofstraße 25" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "80335" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Bayern" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "John Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/more-info" + }, + { + "0173-1#02-AAO204#003": "Manager", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Acme Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Marketing" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "234-567-8901" + }, + "0173-1#02-AAO137#003": "Office", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + }, + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "345-678-9012" + }, + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Montags, 10-16 Uhr" + }, + "0173-1#02-AAO137#003": "Mobile" + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "john.doe@example.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Chat", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "8:00 AM to 6:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Bahnhofstraße 25" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "80335" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Bayern" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "John Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/more-info" + }, + { + "0173-1#02-AAO204#003": "Assistant Manager", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Beta Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Vertrieb" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "123-456-7890" + }, + "0173-1#02-AAO137#003": "assistant", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "jane.doe@betacorp.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info2", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Email", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "9:00 AM to 5:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Ringstraße 2" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "50667" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Nordrhein-Westfalen" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "Jane Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/info2" + } + ] + } + }, + "https://mm-software.com/submodel/000-003/Nameplate": { + "https://admin-shell.io/idta/nameplate/3/0/Nameplate": { + "0112/2///61987#ABA567#009": { + "0112/2///61987#ABA567#009_en": "\u0022ABC-123\u0022" + }, + "0112/2///61987#ABN590#002": "https://mm-software.com/Model-000/Serial-Nr-003", + "0112/2///61987#ABA565#009": { + "0112/2///61987#ABA565#009_de": "M&M" + }, + "0112/2///61987#ABP464#002": { + "0112/2///61987#ABP464#002_en": "M&MType ABC003" + }, + "0112/2///61987#ABA581#007": "MM11-ABC22-003", + "0112/2///61987#ABB757#007": "2025-01-01", + "0112/2///61360_7#AAS006#001": [ + { + "0112/2///61360_7#AAS009#001": [ + { + "0112/2///61987#ABA231#009": "0173-1#07-DAA603#004", + "0112/2///61987#ABH783#003": "KEMA99IECEX1105/128", + "0112/2///61987#ABO097#001": "2022-01-01", + "0112/2///61987#ABH830#002": "2022-01-01", + "0112/2///61987#ABO100#002": "file", + "0112/2///61987#ABB146#007": "0044" + } + ] + } + ], + "0112/2///61987#ABA950#008": "FMABC1234", + "0112/2///61987#ABA951#009": "12345678", + "0112/2///61987#ABP000#002": "2022", + "0112/2///61987#ABB757#007": "2022-01-01", + "0112/2///61987#ABP462#001": "DE", + "0112/2///61987#ABP463#001": "mm-logo", + "0173-1#02-ABI218#003/0173-1#01-AGZ672#004": [ + { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP_en": "\u0022sample\u0022" + }, + "https://mm-software.com/AssetSpecificProperties/ArbitraryFile": "sampleFile", + "0173-1#02-ABI219#003/0173-1#01-AHD205#004": [ + { + "0173-1#01-AHD205#004": [ + { + "0173-1#02-AAO856#002": "", + "https://admin-shell.io/SMT/General/ArbitraryProp": "", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryFile": "TestFile", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP_en": "\u0022sample\u0022" + } + } + ] + } + ] + } + ] + } + }, + "https://mm-software.com/submodel/000-003/Reliability": { + "https://admin-shell.io/idta/iec62683/1/0/Reliability": { + "0112/2///62683#ACE006#001": "4", + "0112/2///62683#ACG071#001": [ + { + "0112/2///61987#ABA969#007": "DC", + "0112/2///61987#ABA588#004": "14.8", + "0112/2///61987#ABD461#004": "3.8", + "0112/2///61987#ABD462#004": "6.7", + "https://admin-shell.io/idta/FunctionalSafety/RatedOperationalCurrent/1/0": "78.9", + "0112/2///62683#ACE053#001": "TestType003", + "0112/2///62683#ACE070#001": "true", + "0112/2///62683#ACE055#001": "150", + "0112/2///62683#ACE054#001": "45" + } + ], + "0112/2///62683#ACG080#001": [ + { + "0112/2///62683#ACE061#001": "2", + "0112/2///62683#ACE062#001": "7", + "https://admin-shell.io/idta/Reliability/B10/1/0": "1" + } + ] + } + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Dockerfile b/source/AAS.TwinEngine.Plugin.TestPlugin/Dockerfile new file mode 100644 index 0000000..f711db5 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Dockerfile @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:aa05b91be697b83229cb000b90120f0783604ad74ed92a0b45cdf3d1a9c873de AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /App +COPY ["AAS.TwinEngine.Plugin.TestPlugin/", "AAS.TwinEngine.Plugin.TestPlugin/"] +RUN dotnet restore "AAS.TwinEngine.Plugin.TestPlugin/AAS.TwinEngine.Plugin.TestPlugin.csproj" +RUN dotnet publish "AAS.TwinEngine.Plugin.TestPlugin/AAS.TwinEngine.Plugin.TestPlugin.csproj" -c "$BUILD_CONFIGURATION" -o out + +FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine@sha256:a0ce42fe86548363a9602c47fc3bd4cf9c35a2705c68cd98d7ce18ae8735b83c +WORKDIR /App + +ENV DATA_DIR=/App/Data +RUN mkdir -p $DATA_DIR +USER app +COPY --from=build /App/out . +ENTRYPOINT ["dotnet", "AAS.TwinEngine.Plugin.TestPlugin.dll"] diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/Manifest/ManifestData.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/Manifest/ManifestData.cs new file mode 100644 index 0000000..3a330d7 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/Manifest/ManifestData.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Manifest; + +public class ManifestData +{ + [JsonPropertyName("supportedSemanticIds")] + public required IList SupportedSemanticIds { get; set; } + + [JsonPropertyName("capabilities")] + public required CapabilitiesData Capabilities { get; set; } +} + +public class CapabilitiesData +{ + [JsonPropertyName("hasShellDescriptor")] + public bool HasShellDescriptor { get; set; } + + [JsonPropertyName("hasAssetInformation")] + public bool HasAssetInformation { get; set; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/MetaData/AssetData.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/MetaData/AssetData.cs new file mode 100644 index 0000000..7eded81 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/MetaData/AssetData.cs @@ -0,0 +1,17 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +public class AssetData +{ + public string? GlobalAssetId { get; set; } + + public List? SpecificAssetIds { get; set; } = []; + + public DefaultThumbnailData? DefaultThumbnail { get; set; } +} + +public class DefaultThumbnailData +{ + public string? Path { get; set; } + + public string? ContentType { get; set; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/MetaData/ShellDescriptorData.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/MetaData/ShellDescriptorData.cs new file mode 100644 index 0000000..33aa027 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/MetaData/ShellDescriptorData.cs @@ -0,0 +1,15 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +public class ShellDescriptorData +{ + public string GlobalAssetId { get; set; } = null!; + public string IdShort { get; set; } = null!; + public string Id { get; set; } = null!; + public List? SpecificAssetIds { get; set; } = []; +} + +public class SpecificAssetIdsData +{ + public string? Name { get; set; } + public string? Value { get; set; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/MetaData/ShellDescriptorsData.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/MetaData/ShellDescriptorsData.cs new file mode 100644 index 0000000..af9e2a7 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/MetaData/ShellDescriptorsData.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +public class ShellDescriptorsData +{ + [JsonPropertyName("paging_metadata")] + public PagingMetaData? PagingMetaData { get; set; } + + [JsonPropertyName("result")] + public IList? Result { get; init; } +} + +public class PagingMetaData +{ + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/Submodel/DataType.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/Submodel/DataType.cs new file mode 100644 index 0000000..e7c526d --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/Submodel/DataType.cs @@ -0,0 +1,13 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +public enum DataType +{ + String, + Number, + Integer, + Object, + Array, + Boolean, + Unknown, + Null +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/Submodel/SemanticTreeNode.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/Submodel/SemanticTreeNode.cs new file mode 100644 index 0000000..73d74f6 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/DomainModel/Submodel/SemanticTreeNode.cs @@ -0,0 +1,31 @@ +using System.Collections.ObjectModel; + +namespace AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +public abstract class SemanticTreeNode(string semanticId, DataType dataType) +{ + public string SemanticId { get; set; } = semanticId; + + public DataType DataType { get; set; } = dataType; +} + +public class SemanticBranchNode(string semanticId, DataType dataType) : SemanticTreeNode(semanticId, dataType) +{ + private readonly List _children = []; + + public ReadOnlyCollection Children => _children.AsReadOnly(); + + public void AddChild(SemanticTreeNode child) => _children.Add(child); + + public void ReplaceChildren(List newChildren) + { + _children.Clear(); + _children.AddRange(newChildren); + } +} + +public class SemanticLeafNode(string semanticId, DataType dataType, string value) : SemanticTreeNode(semanticId, dataType) +{ + public string Value { get; set; } = value; +} + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/README.md b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/README.md new file mode 100644 index 0000000..eac771f --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/README.md @@ -0,0 +1,54 @@ +# TwinEngine Demonstrator Setup + +## Overview +This project provides a basic setup to demonstrate how **TwinEngine** can be integrated and run locally. It provides a complete environment for managing Asset Administration Shells (AAS) and related components. + +This example includes three submodels: + +- Nameplate +- ContactInformation +- Reliability +--- + +## Default configuration + +- `example/aas/` — contains default submodel templates (Nameplate, ContactInformation, Reliability). +- `plugin/`— contain JSON files mounted into the plugin containers: + Changes to these JSON files on the host not visible to the running containers, you must restart the container. + +- Two services are built from local sources in the repo: + +- twinengine-dataengine (built from local DataEngine source) +- twinengine-plugin (plugin build) + +--- +## Rebuild images (when you change code) + +Rebuild all images then restart: + +``` bash +# rebuild images +docker-compose build --no-cache + +# restart the stack +docker-compose up -d +``` +--- + +### Troubleshooting + +- If http://localhost:8080/aas-ui/ doesn't load: + + Check docker-compose logs nginx for errors + + Make sure port **8080, 8081, 8082, 8083, 8085, 8086** is not used by another service. + +- If a container fails to start because of bind port, stop whatever uses that port or change the mapping in `docker-compose.yml`. + +- If you edit plugin JSON files of twinengine-testplugin, make sure you restart that cotainerApp. + ```bash + docker-compose restart + ``` + - Verify the host path for the mounted volume is correct relative to the example folder. + +--- \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/aas/ContactInformationAAS.aas.xml b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/aas/ContactInformationAAS.aas.xml new file mode 100644 index 0000000..5f1c575 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/aas/ContactInformationAAS.aas.xml @@ -0,0 +1,1791 @@ + + + + CustomContactInformationAAS + https://admin-shell.io/idta/aas/ContactInformation/1/0 + + Type + https://admin-shell.io/idta/asset/ContactInformation/1/0 + Type + + + + ModelReference + + + Submodel + https://admin-shell.io/idta/SubmodelTemplate/ContactInformation/1/0 + + + + + + + + + ContactInformations + https://admin-shell.io/idta/SubmodelTemplate/ContactInformation/1/0 + Template + + ModelReference + + + Submodel + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations + + + + + + ContactInformation + + + en + The SMC “ContactInformation” contains information on how to contact the manufacturer or an authorised service provider, e.g. when a maintenance service is required + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation + + + + + + ConceptQualifier + Multiplicity + xs:string + OneToMany + + + + + RoleOfContactPerson + + + en + enumeration: 0173-1#07-AAS927#001 (administrativ contact), 0173-1#07-AAS928#001 (commercial contact), 0173-1#07-AAS929#001 (other contact), 0173-1#07-AAS930#001 (hazardous goods contact), 0173-1#07-AAS931#001 (technical contact). Note: the above mentioned ECLASS enumeration should be declared as “open” for further addition. ECLASS enumeration IRDI is preferable. If no IRDI available, custom input as String may also be accepted. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO204#003 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + xs:string + 0173-1#07-AAS931#001 + + + Company + + ExternalReference + + + GlobalReference + 0173-1#02-AAW001#001 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + ABC Company + + + + + Department + + ExternalReference + + + GlobalReference + 0173-1#02-AAO127#003 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + de + Vertrieb + + + + + Phone + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + TelephoneNumber + + + en + Recommendation: property declaration as MLP is required by its semantic definition. As the property value is language independent, users are recommended to provide maximal 1 string in any language of the user’s choice. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO136#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + One + + + + + en + +491234567890 + + + + + TypeOfTelephone + + + en + enumeration: 0173-1#07-AAS754#001 (office), 0173-1#07-AAS755#001 (office mobile), 0173-1#07-AAS756#001 (secretary), 0173-1#07-AAS757#001 (substitute), 0173-1#07-AAS758#001 (home), 0173-1#07-AAS759#001 (private mobile) + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO137#003 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + xs:string + 0173-1#07-AAS754#001 + + + AvailableTime + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/AvailableTime/ + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + ExternalReference + + + GlobalReference + https://mm-software.com/twinengine/qualifier + + + + TemplateQualifier + InternalSemanticId + xs:string + https://mm-software.com/Phone/AvailableTime + + + + + de + Montag – Freitag 08:00 bis 16:00 + + + + + + + Email + + ExternalReference + + + GlobalReference + 0173-1#02-AAQ836#005 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + EmailAddress + + ExternalReference + + + GlobalReference + 0173-1#02-AAO198#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + One + + + xs:string + email@muster-ag.de + + + TypeOfEmailAddress + + + en + enumeration: 0173-1#07-AAS754#001 (office), 0173-1#07-AAS756#001 (secretary), 0173-1#07-AAS757#001 (substitute), 0173-1#07-AAS758#001 (home) + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO199#003 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + xs:string + 0173-1#07-AAS754#001 + + + + + IPCommunication__00__ + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToMany + + + + + AddressOfAdditionalLink + + ExternalReference + + + GlobalReference + 0173-1#02-AAQ326#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + One + + + + ExternalReference + + + GlobalReference + https://mm-software.com/twinengine/qualifier + + + + TemplateQualifier + InternalSemanticId + xs:string + https://mm-software.com/ipCommunication/AddressOfAdditionalLink + + + xs:string + + + TypeOfCommunication + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + xs:string + Chat + + + AvailableTime + + ExternalReference + + + GlobalReference + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/AvailableTime/ + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + ExternalReference + + + GlobalReference + https://mm-software.com/twinengine/qualifier + + + + TemplateQualifier + InternalSemanticId + xs:string + https://mm-software.com/IPCommunication/AvailableTime + + + + + de + Montag – Freitag 08:00 bis 16:00 + + + + + + + Street + + ExternalReference + + + GlobalReference + 0173-1#02-AAO128#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + de + Musterstraße 1 + + + + + Zipcode + + + en + Recommendation: property declaration as MLP is required by its semantic definition. As the property value is language independent, users are recommended to provide maximal 1 string in any language of the user’s choice. + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO129#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + de + 12345 + + + + + StateCounty + + ExternalReference + + + GlobalReference + 0173-1#02-AAO133#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + de + Muster-Bundesland + + + + + NameOfContact + + ExternalReference + + + GlobalReference + 0173-1#02-AAO205#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + + en + test + + + + + AddressOfAdditionalLink + + ExternalReference + + + GlobalReference + 0173-1#02-AAQ326#002 + + + + + + ConceptQualifier + Multiplicity + xs:string + ZeroToOne + + + + ExternalReference + + + GlobalReference + https://mm-software.com/twinengine/qualifier + + + + TemplateQualifier + InternalSemanticId + xs:string + https://mm-software.com/AddressOfAdditionalLink + + + xs:string + + + + + + + + + RoleOfContactPerson + 0173-1#02-AAO204#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + RoleOfContactPerson + + + + + en + function of a contact person in a process + + + + + + + + + NationalCode + 0173-1#02-AAO134#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + NationalCode + + + + + en + code of a country + + + + + + + + + Language + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Language + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Language + + + + + en + Available language + + + + + + + + + ModelReference + + + Submodel + 0173-1#02-AAO895#003 + + + + + + + CityTown + 0173-1#02-AAO132#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + CityTown + + + + + en + town or city + + + + + + + + + Company + 0173-1#02-AAW001#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Company + + + + + en + name of the company + + + + + + + + + Department + 0173-1#02-AAO127#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Department + + + + + en + administrative section within an organisation where a business partner is located + + + + + + + + + Phone + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Phone + + + + + en + Phone number including type + + + + + + + + + Fax + 0173-1#02-AAQ834#005 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Fax + + + + + en + Fax number including type + + + + + + + + + Email + 0173-1#02-AAQ836#005 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Email + + + + + en + E-mail address and encryption method + + + + + + + + + IPCommunication__00__ + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + IPCommunication_00 + + + + + en + ContactInformation/IPCommunication IP-based communication channels, e.g. chat or video call + + + + + + + + + Street + 0173-1#02-AAO128#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Street + + + + + en + street name and house number + + + + + + + + + Zipcode + 0173-1#02-AAO129#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Zipcode + + + + + en + ZIP code of address + + + + + + + + + POBox + 0173-1#02-AAO130#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + POBox + + + + + en + P.O. box number + + + + + + + + + ZipCodeOfPOBox + 0173-1#02-AAO131#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ZipCodeOfPOBox + + + + + en + ZIP code of P.O. box address + + + + + + + + + StateCounty + 0173-1#02-AAO133#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + StateCounty + + + + + en + federal state a part of a state + + + + + + + + + NameOfContact + 0173-1#02-AAO205#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + NameOfContact + + + + + en + surname of a contact person + + + + + + + + + FirstName + 0173-1#02-AAO206#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + FirstName + + + + + en + first name of a contact person + + + + + + + + + MiddleNames + 0173-1#02-AAO207#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + MiddleNames + + + + + en + middle names of contact person + + + + + + + + + Title + 0173-1#02-AAO208#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Title + + + + + en + common, formal, religious, or other title preceding a contact person's name + + + + + + + + + AcademicTitle + 0173-1#02-AAO209#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + AcademicTitle + + + + + en + academic title preceding a contact person's name + + + + + + + + + FurtherDetailsOfContact + 0173-1#02-AAO210#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + FurtherDetailsOfContact + + + + + en + additional information of the contact person + + + + + + + + + AddressOfAdditionalLink + 0173-1#02-AAQ326#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + AddressOfAdditionalLink + + + + + en + web site address where information about the product or contact is given + + + + + + + + + TelephoneNumber + 0173-1#02-AAO136#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TelephoneNumber + + + + + en + complete telephone number to be called to reach a business partner + + + + + + + + + TypeOfTelephone + 0173-1#02-AAO137#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TypeOfTelephone + + + + + en + characterization of a telephone according to its location or usage + + + + + + + + + AvailableTime + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/AvailableTime/ + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + AvailableTime + + + + + en + Specification of the available time window + + + + + + + + + FaxNumber + 0173-1#02-AAO195#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + FaxNumber + + + + + en + complete telephone number to be called to reach a business partner's fax machine + + + + + + + + + TypeOfFaxNumber + 0173-1#02-AAO196#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TypeOfFaxNumber + + + + + en + characterization of the fax according its location or usage + + + + + + + + + EmailAddress + 0173-1#02-AAO198#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + EmailAddress + + + + + en + electronic mail address of a business partner + + + + + + + + + PublicKey + 0173-1#02-AAO200#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + PublicKey + + + + + en + public part of an unsymmetrical key pair to sign or encrypt text or messages + + + + + + + + + TypeOfEmailAddress + 0173-1#02-AAO199#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TypeOfEmailAddress + + + + + en + characterization of an e-mail address according to its location or usage + + + + + + + + + TypeOfPublicKey + 0173-1#02-AAO201#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TypeOfPublicKey + + + + + en + characterization of a public key according to its encryption process + + + + + + + + + TypeOfCommunication + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TypeOfCommunication + + + + + en + characterization of an IP-based communication channel + + + + + + + + + ContactInformations + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ContactInformations + + + + + en + The Submodel “ContactInformations” is the collection for various contact information. + + + + + + + + + ContactInformation + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ContactInformation + + + + + en + The SMC “ContactInformation” contains information on how to contact the manufacturer or an authorised service provider, e.g. when a maintenance service is required + + + + + + + + + ModelReference + + + Submodel + 0173-1#02-AAQ837#005 + + + + + + + TimeZone + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/TimeZone + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + TimeZone + + + + + en + offsets from Coordinated Universal Time (UTC) + + + + + + + + + \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/aas/DigitalNameplateAAS.aas.xml b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/aas/DigitalNameplateAAS.aas.xml new file mode 100644 index 0000000..2a8f5c5 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/aas/DigitalNameplateAAS.aas.xml @@ -0,0 +1,2395 @@ + + + + DigitalNameplateAAS + https://admin-shell.io/idta/aas/DigitalNameplate/3/0 + + Type + https://admin-shell.io/idta/asset/DigitalNameplate/3/0 + Type + + + + ModelReference + + + Submodel + https://admin-shell.io/idta/SubmodelTemplate/DigitalNameplate/3/0 + + + + + + + + + Nameplate + + + en + Contains the nameplate information attached to the product + + + + 3 + 0 + + https://admin-shell.io/idta/SubmodelTemplate/DigitalNameplate/3/0 + Template + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/nameplate/3/0/Nameplate + + + + + + URIOfTheProduct + + ExternalReference + + + GlobalReference + 0112/2///61987#ABN590#002 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABH173#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + xs:anyURI + https://www.domain-abc.com/Model-Nr-1234/Serial-Nr-5678 + + + ManufacturerName + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA565#009 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO677#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + + + de + "Muster AG" + + + + + ManufacturerProductDesignation + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA567#009 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAW338#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + + + en + "ABC-123" + + + + + ManufacturerProductFamily + + ExternalReference + + + GlobalReference + 0112/2///61987#ABP464#002 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAU731#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + + en + "Type ABC" + + + + + OrderCodeOfManufacturer + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA950#008 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO227#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + xs:string + FMABC1234 + + + ProductArticleNumberOfManufacturer + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA581#007 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO676#005 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + FM11-ABC22-123456 + + + SerialNumber + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA951#009 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAM556#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + 12345678 + + + YearOfConstruction + + ExternalReference + + + GlobalReference + 0112/2///61987#ABP000#002 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAP906#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + 2022 + + + DateOfManufacture + + ExternalReference + + + GlobalReference + 0112/2///61987#ABB757#007 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAR972#004 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:date + 2022-01-01 + + + CountryOfOrigin + + + en + Note: Country codes defined accord. to DIN EN ISO 3166-1 alpha-2 codes + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABP462#001 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAO259#007 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + DE + + + CompanyLogo + + ExternalReference + + + GlobalReference + 0112/2///61987#ABP463#001 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI776#002 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + image/png + + + Markings + + + en + Note: CE marking is declared as mandatory according to EU Blue Guide + + + + ExternalReference + + + GlobalReference + 0112/2///61360_7#AAS006#001 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI563#003/0173-1#01-AHF849#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + SubmodelElementCollection + + + + + en + Note: CE marking is declared as mandatory according to the Blue Guide of the EU-Commission + + + + ExternalReference + + + GlobalReference + 0112/2///61360_7#AAS009#001 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI564#003/0173-1#01-AHF850#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + OneToMany + + + + + MarkingName + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA231#009 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI190#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + xs:string + 0173-1#07-DAA603#004 + + + DesignationOfCertificateOrApproval + + + en + Note: Approval identifier, reference to the certificate number, to be entered without spaces + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABH783#003 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI975#002 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:string + KEMA99IECEX1105/128 + + + IssueDate + + + en + Note: format by lexical representation: CCYY-MM-DD Note: to be specified to the day + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABO097#001 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABL774#001 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:date + 2022-01-01 + + + ExpiryDate + + + en + Note: format by lexical representation: CCYY-MM-DD Note: to be specified to the day + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABH830#002 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABL775#001 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + xs:date + 2022-01-01 + + + MarkingFile + + ExternalReference + + + GlobalReference + 0112/2///61987#ABO100#002 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI191#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + image/png + + + MarkingAdditionalText + + ExternalReference + + + GlobalReference + 0112/2///61987#ABB146#007 + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-ABI192#003 + + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:string + 0044 + + + + + + + AssetSpecificProperties + + ExternalReference + + + GlobalReference + 0173-1#02-ABI218#003/0173-1#01-AGZ672#004 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + + ArbitraryProperty + + + en + Note: Every property can be used. + + + en + Note: The idShort is arbitrary + + + en + Note: The use of a displayName is recommended. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SMT/General/ArbitraryProp + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:string + + + ArbitraryMLP + + + en + Note: Every multilanguage property can be used. + + + en + Note: The idShort is arbitrary + + + en + Note: The use of a displayName is recommended. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SMT/General/ArbitraryMLP + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + + ExternalReference + + + GlobalReference + https://mm-software.com/twinengine/qualifier + + + + TemplateQualifier + InternalSemanticId + xs:string + https://mm-software.com/AssetSpecificProperties/ArbitraryMLP + + + + + en + "sample" + + + + + ArbitraryFile + + + en + Note: Every file can be used. + + + en + The idShort is arbitrary + + + en + Note: The use of a displayName is recommended. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SMT/General/ArbitraryFile + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + + ExternalReference + + + GlobalReference + https://mm-software.com/twinengine/qualifier + + + + InternalSemanticId + xs:string + https://mm-software.com/AssetSpecificProperties/ArbitraryFile + + + application/pdf + + + GuidelineSpecificProperties + + ExternalReference + + + GlobalReference + 0173-1#02-ABI219#003/0173-1#01-AHD205#004 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + SubmodelElementCollection + + + + ExternalReference + + + GlobalReference + 0173-1#01-AHD205#004 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + OneToMany + + + + + GuidelineForConformityDeclaration + + ExternalReference + + + GlobalReference + 0173-1#02-AAO856#002 + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + One + + + xs:string + + + ArbitraryProperty + + + en + Note: Every property can be used. + + + en + Note: The idShort is arbitrary + + + en + Note: The use of a displayName is recommended. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SMT/General/ArbitraryProp + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:string + + + ArbitraryFile + + + en + Note: Every file can be used. + + + en + The idShort is arbitrary + + + en + Note: The use of a displayName is recommended. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SMT/General/ArbitraryFile + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + + ExternalReference + + + GlobalReference + https://mm-software.com/twinengine/qualifier + + + + InternalSemanticId + xs:string + https://mm-software.com/GuidelineSpecificProperties/ArbitraryFile + + + image/png + + + ArbitraryMLP + + + en + Note: Every multilanguage property can be used. + + + en + Note: The idShort is arbitrary + + + en + Note: The use of a displayName is recommended. + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SMT/General/ArbitraryMLP + + + + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/SubmodelTemplates/Cardinality/1/0 + + + + TemplateQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + + ExternalReference + + + GlobalReference + https://mm-software.com/twinengine/qualifier + + + + TemplateQualifier + InternalSemanticId + xs:string + https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP + + + + + en + "sample" + + + + + + + + + + + + + + + URIOfTheProduct + 0112/2///61987#ABN590#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + URIOfTheProduct + + + + + en + URIOfTheProduct + + + STRING + + + en + unique global identification of the product instance using an universal resource identifier (URI) + + + + + + + + + ManufacturerName + 0112/2///61987#ABA565#009 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ManufacturerName + + + STRING_TRANSLATABLE + + + en + legally valid designation of the natural or judicial person which is directly responsible for the design, production, packaging and labeling of a product in respect to its being brought into circulation + + + + + + + + + ManufacturerProductDesignation + 0112/2///61987#ABA567#009 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ManufacturerProductDesignation + + + STRING_TRANSLATABLE + + + en + short description of the product (short text), third or lowest level of a 3 level manufacturer specific product hierarchy + + + + + + + + + ManufacturerProductRoot + 0112/2///61360_7#AAS011#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ManufacturerProductRoot + + + STRING_TRANSLATABLE + + + en + top level of a 3 level manufacturer specific product hierarchy + + + + + + + + + ManufacturerProductFamily + 0112/2///61987#ABP464#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ManufacturerProductFamily + + + STRING_TRANSLATABLE + + + en + second level of a 3 level manufacturer specific product hierarchy + + + + + + + + + ManufacturerProductType + 0112/2///61987#ABA300#008 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ManufacturerProductType + + + STRING + + + en + characteristic to differentiate between different products of a product family or special variants + + + + + + + + + OrderCodeOfManufacturer + 0112/2///61987#ABA950#008 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + OrderCodeOfManufacturer + + + STRING + + + en + unique combination of numbers and letters issued by the manufacturer that is used to identify the device for ordering + + + + + + + + + ProductArticleNumberOfManufacturer + 0112/2///61987#ABA581#007 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ProductArticleNumberOfManufacturer + + + STRING + + + en + unique product identifier of the manufacturer + + + + + + + + + SerialNumber + 0112/2///61987#ABA951#009 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + SerialNumber + + + STRING + + + en + unique combination of numbers and letters used to identify the device once it has been manufactured + + + + + + + + + YearOfConstruction + 0112/2///61987#ABP000#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + YearOfConstruction + + + STRING + + + en + year in which the manufacturing process is completed + + + + + + + + + DateOfManufacture + 0112/2///61987#ABB757#007 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + DateOfManufacture + + + DATE + + + en + date when an item was manufactured + + + + + + + + + HardwareVersion + 0112/2///61987#ABA926#008 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + HardwareVersion + + + STRING + + + en + version of the hardware supplied with the device + + + + + + + + + FirmwareVersion + 0112/2///61987#ABA302#006 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + FirmwareVersion + + + STRING + + + en + version of the firmware supplied with the device + + + + + + + + + SoftwareVersion + 0112/2///61987#ABA601#008 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + SoftwareVersion + + + STRING + + + en + version of the software used by the device + + + + + + + + + CountryOfOrigin + 0112/2///61987#ABP462#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + CountryOfOrigin + + + STRING + + + en + country where the product was manufactured + + + + + + + + + CompanyLogo + 0112/2///61987#ABP463#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + CompanyLogo + + + STRING + + + en + a graphic mark used to represent a company, an organisation or a product + + + + + + + + + AssetSpecificProperties + 0173-1#01-AGZ672#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + AssetSpecificProperties + + + + + en + Group of properties that are listed on the asset's nameplate and are grouped based on guidelines + + + + + + + + + MarkingName + 0112/2///61987#ABA231#009 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + MarkingName + + + STRING + + + en + common name of the marking + + + + + + + + + DesignationOfCertificateOrApproval + 0112/2///61987#ABH783#003 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + DesignationOfCertificateOrApproval + + + STRING + + + en + alphanumeric character sequence identifying a certificate or approval + + + + + + + + + IssueDate + 0112/2///61987#ABO097#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + IssueDate + + + DATE + + + en + date, at which the specified certificate is issued + + + + + + + + + ExpiryDate + 0112/2///61987#ABH830#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ExpiryDate + + + DATE + + + en + date, at which the specified certificate expires + + + + + + + + + MarkingFile + 0112/2///61987#ABO100#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + MarkingFile + + + STRING + + + en + conformity symbol of the marking + + + + + + + + + MarkingAdditionalText__00__ + 0112/2///61987#ABB146#007 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + MarkingAdditionalText__00__ + + + STRING + + + en + where applicable, additional information on the marking in plain text, e.g. the ID-number of the notified body involved in the conformity process + + + + + + + + + GuidelineSpecificProperties + 0173-1#01-AHD205#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + GuidelineSpecificProperties + + + + + en + Asset specific nameplate information required by guideline, stipulation or legislation. + + + + + + + + + GuidelineForConformityDeclaration + 0173-1#02-AAO856#002 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + GuidelineForConformityDeclaration + + + + + en + guideline, stipulation or legislation used for determining conformity + + + + + + + + + ContactInformation + https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + ContactInformation + + + + + en + The SMC “ContactInformation” contains information on how to contact the manufacturer or an authorised service provider, e.g. when a maintenance service is required + + + + + + + + + ExternalReference + + + GlobalReference + 0173-1#02-AAQ837#005 + + + + + + + Markings + + + en + Note: CE marking is declared as mandatory according to EU Machine Directive 2006/42/EC. + + + https://admin-shell.io/zvei/nameplate/3/0/Nameplate/Markings + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Markings + + + + + en + Collection of product markings + + + + + + + + + Marking + + + en + Note: CE marking is declared as mandatory according to the Blue Guide of the EU-Commission + + + 0112/2///61360_7#AAS009#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + Marking + + + + + en + Single marking information + + + + + + + + + GuidelineSpecificProperties + http://admin-shell.io/IDTA/DigitalNameplate/GuidelineSpecificProperties/List/1/0 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + GuidelineSpecificProperties + + + + + en + Information elements guided by a specific standard or guideline + + + + + + + + + UniqueFacilityIdentifier + https://admin-shell.io/idta/nameplate/3/0/UniqueFacilityIdentifier + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0 + + + + + + + + en + UniqueFacilityIdentifier + + + STRING + + + en + unique string of characters for the identification of locations or buildings involved in a product’s value chain or used by actors involved in a product’s value chain + + + + + + + + + \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/aas/Reliability.xml b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/aas/Reliability.xml new file mode 100644 index 0000000..25e8158 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/aas/Reliability.xml @@ -0,0 +1,1471 @@ + + + + CustomReliabilityAAS + https://admin-shell.io/idta/aas/Reliability/1/0 + + Type + https://admin-shell.io/idta/asset/Reliability/1/0 + Type + + + + ModelReference + + + Submodel + https://admin-shell.io/idta/SubmodelTemplate/Reliability/1/0 + + + + + + + + + Reliability + + 1 + 0 + + https://admin-shell.io/idta/SubmodelTemplate/Reliability/1/0 + Template + + ModelReference + + + Submodel + https://admin-shell.io/idta/iec62683/1/0/Reliability + + + + + + NumberOfReliabilitySets + + + en + number of reliability sets of characteristics + + + fr + nombre d'ensembles de caractéristiques de fiabilité + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE006#001 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + One + + + xs:int + + + OperatingConditionsOfReliabilityCharacteristics + + + en + operating conditions of reliability characteristics + + + fr + conditions de fonctionnement des caractéristiques de fiabilité et de sécurité fonctionnelle + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACG071#001 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + + + TypeOfVoltage + + + en + type of voltage + + + fr + type de tension + + + de + Spannungsart + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA969#007 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:string + + ExternalReference + + + GlobalReference + 0112/2///61987#ABL837#001 + + + GlobalReference + 0112/2///61987#ABL838#001 + + + GlobalReference + 0112/2///61987#ABI407#004 + + + + + + RatedVoltage + + + en + rated voltage + + + fr + tension assignée + + + de + Bemessungsspannung + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA588#004 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:double + + + MinimumRatedVoltage + + + en + minimum rated voltage + + + fr + tension assignée minimale + + + de + minimale Bemessungsspannung + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABD461#004 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:double + + + MaximumRatedVoltage + + + en + maximum rated voltage + + + fr + tension assignée maximale + + + de + maximale Bemessungsspannung + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABD462#004 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:double + + + RatedOperationalCurrent + + + en + rated operational current + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/FunctionalSafety/RatedOperationalCurrent/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:double + + + TypeOfInterlockingDevice + + + en + type of interlocking device + + + fr + type de dispositif de verrouillage + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE053#001 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:string + + ExternalReference + + + GlobalReference + 0112/2///62683#ACH673#001 + + + GlobalReference + 0112/2///62683#ACH674#001 + + + GlobalReference + 0112/2///62683#ACH675#001 + + + GlobalReference + 0112/2///62683#ACH676#001 + + + + + + OtherOperatingConditions + + + en + other operating conditions + + + fr + autres conditions de fonctionnement + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE070#001 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:string + + + UsefulLifeInNumberOfOperations + + + en + useful life in number of operations + + + fr + durée de vie utile en cycle de fonctionnement + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE055#001 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:double + + + UsefulLifeInTimeInterval + + + en + useful life in time interval + + + fr + durée de vie utile en intervalle de temps + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE054#001 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:double + + + + + ReliabilityCharacteristics + + + en + Reliability characteristics + + + fr + Caractéristiques de fiabilité + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACG080#001 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToOne + + + + + MTTF + + + en + mean operating time to failure + + + fr + durée moyenne de fonctionnement avant défaillance + + + de + mittlere Betriebszeit bis zum Ausfall + + + jp + 平均故障間動作時間 + + + cn + 平均失效前工作时间 + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE061#001 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:int + + + MTBF + + + en + mean operating time between failure + + + fr + moyenne des temps de bon fonctionnement + + + de + mittlere Betriebszeit zwischen Ausfällen + + + cn + 平均失效间隔工作时间 + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE062#001 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:int + + + B10cycle + + + en + B10 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/Reliability/B10/1/0 + + + + + + ConceptQualifier + SMT/Cardinality + xs:string + ZeroToMany + + + xs:int + + + + + + + + + NumberOfReliabilitySetsOfCharacteristics + 0112/2///62683#ACE006#001 + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE006#001 + + + + + + + + en + number of reliability sets of characteristics + + + fr + nombre d'ensembles de caractéristiques de fiabilité + + + + + en + NoOfReliSets + + + NR1..2 + + + + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE006#001 + + + + + + + ReliabilityCharacteristics + 0112/2///62683#ACG080#001 + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACG080#001 + + + + + + + + en + Reliability characteristics + + + fr + Caractéristiques de fiabilité + + + + + en + ReliabilityChar + + + + + en + characteristics of a subsystem or a subsystem element intended for evaluating its ability to perform as required, without failure, for a given time interval, under given conditions + + + + + + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACG080#001 + + + + + + + MeanOperatingTimeToFailure + 0112/2///62683#ACE061#001 + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE061#001 + + + + + + + + en + mean operating time to failure + + + fr + durée moyenne de fonctionnement avant défaillance + + + de + mittlere Betriebszeit bis zum Ausfall + + + jp + 平均故障間動作時間 + + + cn + 平均失效前工作时间 + + + + + en + MTTF + + + y + + ExternalReference + + + GlobalReference + 0112/2///62720#UAB026 + + + + MTTF + + + en + expectation of the operating time to failure + + + NR1..7 + + + + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE061#001 + + + + + + + MeanOperatingTimeBetweenFailure + 0112/2///62683#ACE062#001 + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE062#001 + + + + + + + + en + mean operating time between failure + + + fr + moyenne des temps de bon fonctionnement + + + de + mittlere Betriebszeit zwischen Ausfällen + + + cn + 平均失效间隔工作时间 + + + + + en + MTBF + + + y + + ExternalReference + + + GlobalReference + 0112/2///62720#UAB026 + + + + MTBF + + + en + expectation of the duration of the operating time between failures + + + NR1..7 + + + + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE062#001 + + + + + + + B10 + https://admin-shell.io/idta/Reliability/B10/1/0 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + B10 + + + + + en + B10 + + + + + en + mean number of cycles until 10% of the components fail + + + + + + + + + OperatingConditionsOfReliabilityCharacteristics + 0112/2///62683#ACG071#001 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + operating conditions of reliability characteristics + + + fr + conditions de fonctionnement des caractéristiques de fiabilité et de sécurité fonctionnelle + + + + + + + + + TypeOfVoltage + 0112/2///61987#ABA969#007 + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA969#007 + + + + + + + + en + type of voltage + + + fr + type de tension + + + de + Spannungsart + + + + + en + type of voltage + + + + + en + classification of a power supply according to the time behaviour of the voltage + + + + + + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA969#007 + + + + + + + RatedVoltage + 0112/2///61987#ABA588#004 + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA588#004 + + + + + + + + en + rated voltage + + + fr + tension assignée + + + de + Bemessungsspannung + + + + + en + RatedVoltage + + + V + + ExternalReference + + + GlobalReference + 0112/2///62720#UAA296 + + + + + + en + operating voltage of the device as defined by the manufacturer and to which certain device properties are referenced + + + + + + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABA588#004 + + + + + + + OperatingConditionsOfFunctionalSafetyCharacteristics + 0112/2///62683#ACG057#001 + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACG057#001 + + + + + + + + en + Operating conditions of functional safety characteristics + + + fr + Conditions de fonctionnement des caractéristiques de sécurité fonctionnelle + + + + + + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACG057#001 + + + + + + + MinimumRatedVoltage + 0112/2///61987#ABD461#004 + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABD461#004 + + + + + + + + en + minimum rated voltage + + + fr + tension assignée minimale + + + de + minimale Bemessungsspannung + + + + + en + MinRatedVol + + + V + + ExternalReference + + + GlobalReference + 0112/2///62720#UAA296 + + + + + + en + lowest operating voltage of the device as defined by the manufacturer + + + + + + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABD461#004 + + + + + + + MaximumRatedVoltage + 0112/2///61987#ABD462#004 + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABD462#004 + + + + + + + + en + maximum rated voltage + + + fr + tension assignée maximale + + + de + maximale Bemessungsspannung + + + + + en + MaxRatVol + + + V + + ExternalReference + + + GlobalReference + 0112/2///62720#UAA296 + + + + + + en + highest operating voltage of the device as defined by the manufacturer + + + + + + + + + ExternalReference + + + GlobalReference + 0112/2///61987#ABD462#004 + + + + + + + RatedOperationalCurrent + https://admin-shell.io/idta/FunctionalSafety/RatedOperationalCurrent/1/0 + + + + ExternalReference + + + GlobalReference + http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0 + + + + + + + + en + rated operational current + + + + + en + RatOperCurrent + + + mA + + ExternalReference + + + GlobalReference + 0112/2///62720#UAA775 + + + + + + en + current combined with a rated operational voltage intended to be switched by the device under specified conditions + + + + + + + + + TypeOfInterlockingDevice + 0112/2///62683#ACE053#001 + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE053#001 + + + + + + + + en + type of interlocking device + + + fr + type de dispositif de verrouillage + + + + + en + classification of device which prevent the hazardous operation of machine, depending on the technology of their actuating means and their output system + + + X.6 + + + + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE053#001 + + + + + + + OtherOperatingConditions + 0112/2///62683#ACE070#001 + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE070#001 + + + + + + + + en + other operating conditions + + + fr + autres conditions de fonctionnement + + + + + en + other limits of operation related to functional safety characteristics + + + X..256 + + + + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE070#001 + + + + + + + UsefulLifeInNumberOfOperations + 0112/2///62683#ACE055#001 + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE055#001 + + + + + + + + en + useful life in number of operations + + + fr + durée de vie utile en cycle de fonctionnement + + + + + en + under given conditions, the number of operations for which the failure rate becomes unacceptable + + + NR3..1.2E1 + + + + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE055#001 + + + + + + + UsefulLifeInTimeInterval + 0112/2///62683#ACE054#001 + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE054#001 + + + + + + + + en + useful life in time interval + + + fr + durée de vie utile en intervalle de temps + + + + + en + LifeInTime + + + y + + ExternalReference + + + GlobalReference + 0112/2///62720#UAB026 + + + + + + en + under given conditions, the time interval beginning at a given instant of time, and ending when the failure rate becomes unacceptable + + + NR2..2.1 + + + + + + + ExternalReference + + + GlobalReference + 0112/2///62683#ACE054#001 + + + + + + + \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get All ShellDescriptors.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get All ShellDescriptors.bru new file mode 100644 index 0000000..1fc5791 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get All ShellDescriptors.bru @@ -0,0 +1,21 @@ +meta { + name: Get All ShellDescriptors + type: http + seq: 1 +} + +get { + url: {{DataEngineBaseUrl}}/shell-descriptors?limit&cursor + body: none + auth: inherit +} + +params:query { + limit: + cursor: +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/apiCollection/Aas Registry/Get Shell Descriptor By Id.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get Shell Descriptor By Id.bru similarity index 100% rename from apiCollection/Aas Registry/Get Shell Descriptor By Id.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get Shell Descriptor By Id.bru diff --git a/apiCollection/Aas Registry/folder.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/folder.bru similarity index 100% rename from apiCollection/Aas Registry/folder.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/folder.bru diff --git a/apiCollection/Aas Repository/Get Asset Information By Id.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Asset Information By Id.bru similarity index 100% rename from apiCollection/Aas Repository/Get Asset Information By Id.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Asset Information By Id.bru diff --git a/apiCollection/Aas Repository/Get Shell By Id.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Shell By Id.bru similarity index 100% rename from apiCollection/Aas Repository/Get Shell By Id.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Shell By Id.bru diff --git a/apiCollection/Aas Repository/Get Submodel Ref By Id.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel Ref By Id.bru similarity index 100% rename from apiCollection/Aas Repository/Get Submodel Ref By Id.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel Ref By Id.bru diff --git a/apiCollection/Aas Repository/folder.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/folder.bru similarity index 100% rename from apiCollection/Aas Repository/folder.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/folder.bru diff --git a/apiCollection/README.md b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/README.md similarity index 85% rename from apiCollection/README.md rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/README.md index 15d4f1a..56a2e83 100644 --- a/apiCollection/README.md +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/README.md @@ -6,20 +6,15 @@ This directory contains the Bruno collection and instructions to test the **AAS. --- -## 📚 Contents - -[[_TOC_]] - - -## 🔍 Quick Summary +## Quick Summary | Item | Description | |--------------------------|-----------------------------------------------------| | **API** | `AAS.TwinEngine.DataEngine` (.NET) | | **Testing Tool** | [Bruno](https://www.usebruno.com/downloads) | -| **Default API URL** | `https://localhost:5059` | +| **Default API URL** | `local` `http://localhost:8085`
- started with Docker compose
`local_net` `https://localhost:5059`
- started with devleopment environment (dotnet run) | | **SDK Required** | .NET 8 (recommended) | -| **Run docker compose file** | Run `docker-compose-up` form AasTwin.DataEngine | +| **Run docker compose file** | Run `docker-compose up -d` from AAS.TwinEngine.Plugin.TestPlugin Example folder | --- @@ -43,11 +38,13 @@ This directory contains the Bruno collection and instructions to test the **AAS. ### 1. Run docker compose file -Before starting , run twinengine environmnet with multi-plugin. +Before starting , run twinengine environmnet with plugin. [click here for getting starated with docker-compose](../README.md) ### 2. Start the DataEngine .NET API +**Annotation:** To avoid complications you should deactivate the running DataEngine started with Docker compose. + Run the API: ```bash @@ -63,7 +60,7 @@ By default the API listens at `https://localhost:5059` unless overridden by envi 1. Open Bruno 2. `Collection -> Open Collection` and choose the Bruno collection folder (`apiCollection`) from the AasTwin.DataEngine repository -3. From the top-right environment dropdown select an environment: `local` or `dev` (use `local` for local testing) +3. From the top-right environment dropdown select an environment: `local` or `local_net` (use `local_net` for local testing with development environment) 4. Expand folders to find requests, select a request and click **Send** 5. Inspect the request/response in the right panel @@ -88,17 +85,16 @@ Default values are set as shown. ## Default api-test configuration -* The default configuration includes four shell descriptors with these IDs: +* The default configuration includes three shell descriptors with these IDs: * `https://mm-software.com/ids/aas/000-001` * `https://mm-software.com/ids/aas/000-002` * `https://mm-software.com/ids/aas/000-003` - * `https://mm-software.com/ids/aas/000-004` -* Default submodel templates (under `/infrastructure/config-files/aas`): +* Default submodel templates (under `/Example/aas`): - * `ContactInformation` - * `Nameplate` + * `ContactInformationAAS` + * `DigitalNameplateAAS` * `Reliability` * Default shell template used by all four shells: @@ -150,7 +146,7 @@ By default, DataEngine requests the dev Template Repository, Submodel Registry, ## Troubleshooting -#### ❌ Bruno shows `SSL/TLS handshake failed` +#### Bruno shows `SSL/TLS handshake failed` - Run `dotnet dev-certs https --trust` - Ensure plugin and API endpoints match port and schema (`https://`) diff --git a/apiCollection/Submodel Registry/Get Submodel Descriptor By Id.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id.bru similarity index 100% rename from apiCollection/Submodel Registry/Get Submodel Descriptor By Id.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Registry/Get Submodel Descriptor By Id.bru diff --git a/apiCollection/Submodel Registry/folder.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Registry/folder.bru similarity index 100% rename from apiCollection/Submodel Registry/folder.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Registry/folder.bru diff --git a/apiCollection/Submodel Repository/Serialization/Get appropriate serialization.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Serialization/Get appropriate serialization.bru similarity index 100% rename from apiCollection/Submodel Repository/Serialization/Get appropriate serialization.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Serialization/Get appropriate serialization.bru diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Serialization/folder.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Serialization/folder.bru new file mode 100644 index 0000000..d303a46 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Serialization/folder.bru @@ -0,0 +1,8 @@ +meta { + name: Serialization + seq: 3 +} + +auth { + mode: inherit +} diff --git a/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - ContactInfo.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - ContactInfo.bru similarity index 100% rename from apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - ContactInfo.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - ContactInfo.bru diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Markings.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Markings.bru new file mode 100644 index 0000000..c87b942 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Markings.bru @@ -0,0 +1,16 @@ +meta { + name: Get Submodel Element - Markings + type: http + seq: 2 +} + +get { + url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier/submodel-elements/:idShortPath + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierNameplate}} + idShortPath: Markings +} diff --git a/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Nameplate.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Nameplate.bru similarity index 100% rename from apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Nameplate.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Nameplate.bru diff --git a/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Reliability.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Reliability.bru similarity index 100% rename from apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Reliability.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel Element/Get Submodel Element - Reliability.bru diff --git a/apiCollection/Submodel Repository/Submodel Element/folder.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel Element/folder.bru similarity index 100% rename from apiCollection/Submodel Repository/Submodel Element/folder.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel Element/folder.bru diff --git a/apiCollection/Submodel Repository/Submodel/Get Submodel - ContactInfo.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel/Get Submodel - ContactInfo.bru similarity index 100% rename from apiCollection/Submodel Repository/Submodel/Get Submodel - ContactInfo.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel/Get Submodel - ContactInfo.bru diff --git a/apiCollection/Submodel Repository/Submodel/Get Submodel - Nameplate.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel/Get Submodel - Nameplate.bru similarity index 100% rename from apiCollection/Submodel Repository/Submodel/Get Submodel - Nameplate.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel/Get Submodel - Nameplate.bru diff --git a/apiCollection/Submodel Repository/Submodel/Get Submodel - Reliability.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel/Get Submodel - Reliability.bru similarity index 100% rename from apiCollection/Submodel Repository/Submodel/Get Submodel - Reliability.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel/Get Submodel - Reliability.bru diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel/folder.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel/folder.bru new file mode 100644 index 0000000..03deb2e --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/Submodel/folder.bru @@ -0,0 +1,8 @@ +meta { + name: Submodel + seq: 1 +} + +auth { + mode: inherit +} diff --git a/apiCollection/Submodel Repository/folder.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/folder.bru similarity index 100% rename from apiCollection/Submodel Repository/folder.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Submodel Repository/folder.bru diff --git a/apiCollection/bruno.json b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/bruno.json similarity index 100% rename from apiCollection/bruno.json rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/bruno.json diff --git a/apiCollection/collection.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/collection.bru similarity index 100% rename from apiCollection/collection.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/collection.bru diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/environments/local.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/environments/local.bru new file mode 100644 index 0000000..daae894 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/environments/local.bru @@ -0,0 +1,3 @@ +vars { + DataEngineBaseUrl: http://localhost:8085 +} diff --git a/apiCollection/environments/local.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/environments/local_net.bru similarity index 100% rename from apiCollection/environments/local.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/environments/local_net.bru diff --git a/apiCollection/health.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/health.bru similarity index 100% rename from apiCollection/health.bru rename to source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/health.bru diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/basyx/aas-env.properties b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/basyx/aas-env.properties new file mode 100644 index 0000000..2c3b05d --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/basyx/aas-env.properties @@ -0,0 +1,16 @@ +server.port=8081 +basyx.environment=file:aas +basyx.cors.allowed-origins=* +basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD +basyx.aasrepository.feature.registryintegration=http://aas-template-registry:8080 +basyx.submodelrepository.feature.registryintegration=http://sm-template-regisry:8080 +spring.servlet.multipart.max-file-size=128MB +spring.servlet.multipart.max-request-size=128MB +basyx.backend=MongoDB +spring.data.mongodb.host=mongo +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aasenvironment +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/docker-compose.yml b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/docker-compose.yml new file mode 100644 index 0000000..a4a0ae1 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/docker-compose.yml @@ -0,0 +1,183 @@ +networks: + dataengine-network: + name: dataengine-network + +services: + nginx: + image: nginx:latest + container_name: nginx + ports: + - "8080:80" + volumes: + - ./nginx/html:/usr/share/nginx/html + - ./nginx/default.conf.template:/etc/nginx/templates/default.conf.template + restart: always + depends_on: + aas-template-registry: + condition: service_healthy + sm-template-regisry: + condition: service_healthy + template-repository: + condition: service_healthy + networks: + - dataengine-network + + twinengine-dataengine: + build: + context: ../../ + dockerfile: ./AAS.TwinEngine.DataEngine/Dockerfile + image: twinengine-dataengine:latest + ports: + - '8085:8080' + container_name: twinengine-dataengine + depends_on: + - mongo + restart: always + environment: + - AasEnvironment__DataEngineRepositoryBaseUrl=http://localhost:8080 + - AasEnvironment__AasEnvironmentRepositoryBaseUrl=http://template-repository:8081 + - AasEnvironment__SubModelRepositoryPath=submodels + - AasEnvironment__AasRegistryBaseUrl=http://aas-template-registry:8080 + - AasEnvironment__AasRegistryPath=shell-descriptors + - AasEnvironment__SubModelRegistryBaseUrl=http://sm-template-regisry:8080 + - AasEnvironment__SubModelRegistryPath=submodel-descriptors + - AasEnvironment__AasRepositoryPath=shells + - AasEnvironment__SubmodelRefPath=submodel-refs + - AasEnvironment__CustomerDomainUrl=https://mm-software.com + - TemplateMappingRules__SubmodelTemplateMappings__0__templateId=https://admin-shell.io/idta/SubmodelTemplate/Reliability/1/0 + - TemplateMappingRules__SubmodelTemplateMappings__0__pattern__0=Reliability + - TemplateMappingRules__SubmodelTemplateMappings__1__templateId=https://admin-shell.io/idta/SubmodelTemplate/DigitalNameplate/3/0 + - TemplateMappingRules__SubmodelTemplateMappings__1__pattern__0=Nameplate + - TemplateMappingRules__SubmodelTemplateMappings__2__templateId=https://admin-shell.io/idta/SubmodelTemplate/ContactInformation/1/0 + - TemplateMappingRules__SubmodelTemplateMappings__2__pattern__0=ContactInformation + - TemplateMappingRules__ShellTemplateMappings__0__templateId=https://mm-software.com/aas/aasTemplate + - TemplateMappingRules__ShellTemplateMappings__0__pattern__0= + - TemplateMappingRules__AasIdExtractionRules__0__pattern=Regex + - TemplateMappingRules__AasIdExtractionRules__0__index=6 + - TemplateMappingRules__AasIdExtractionRules__0__separator=/ + - Semantics__MultiLanguageSemanticPostfixSeparator=_ + - Semantics__SubmodelElementIndexContextPrefix=_aastwinengineindex_ + - Semantics__InternalSemanticId=InternalSemanticId + - AasxOptions__RootFolder=aasx + - AasRegistryPreComputed__ShellDescriptorCron=0 */3 * * * * + - AasRegistryPreComputed__IsPreComputed=false + - MultiPluginConflictOption__HandlingMode=TakeFirst + - PluginConfig__Plugins__0__PluginName=Plugin + - PluginConfig__Plugins__0__PluginUrl=http://twinengine-plugin:8080 + networks: + - dataengine-network + + twinengine-plugin: + build: + context: ../../ + dockerfile: ./AAS.TwinEngine.Plugin.TestPlugin/Dockerfile + image: twinengine-plugin:latest + ports: + - '8086:8080' + container_name: twinengine-plugin + restart: always + volumes: + - ./plugin/mock-metadata.json:/App/Data/mock-metadata.json + - ./plugin/mock-submodel-data.json:/App/Data/mock-submodel-data.json + environment: + - ASPNETCORE_ENVIRONMENT=Production + - Semantics__IndexContextPrefix=_aastwinengineindex_ + - Capabilities__HasShellDescriptor=true + - Capabilities__HasAssetInformation=true + networks: + - dataengine-network + + template-repository: + image: eclipsebasyx/aas-environment:2.0.0-SNAPSHOT + container_name: template-repository + ports: + - "8081:8081" + volumes: + - ./aas:/application/aas + - ./basyx/aas-env.properties:/application/application.properties + environment: + BASYX_EXTERNALURL : http://localhost:8080 + restart: always + depends_on: + aas-template-registry: + condition: service_healthy + sm-template-regisry: + condition: service_healthy + networks: + - dataengine-network + + aas-template-registry: + image: eclipsebasyx/aas-registry-log-mongodb:2.0.0-SNAPSHOT + container_name: aas-template-registry + ports: + - '8082:8080' + restart: always + depends_on: + - mongo + environment: + - SPRING_DATA_MONGODB_URI=mongodb://mongoAdmin:mongoPassword@mongo:27017 + networks: + - dataengine-network + + sm-template-regisry: + image: eclipsebasyx/submodel-registry-log-mongodb:2.0.0-SNAPSHOT + container_name: sm-template-regisry + ports: + - '8083:8080' + depends_on: + - mongo + environment: + - SPRING_DATA_MONGODB_URI=mongodb://mongoAdmin:mongoPassword@mongo:27017 + networks: + - dataengine-network + + shell-template-creator: + image: curlimages/curl:latest + container_name: shell-template-creator + depends_on: + template-repository: + condition: service_healthy + entrypoint: > + sh -c ' + echo "Waiting for template-repository to be ready..."; + sleep 10; + curl -X POST http://template-repository:8081/shells \ + -H "accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"id\": \"https://mm-software.com/aas/aasTemplate\", \"assetInformation\": {\"assetKind\": \"Instance\"}, \"submodels\": [{\"type\": \"ModelReference\", \"keys\": [{\"type\": \"Submodel\", \"value\": \"Nameplate\"}]}, {\"type\": \"ModelReference\", \"keys\": [{\"type\": \"Submodel\", \"value\": \"ContactInformation\"}]}, {\"type\": \"ModelReference\", \"keys\": [{\"type\": \"Submodel\", \"value\": \"Reliability\"}]}], \"modelType\": \"AssetAdministrationShell\"}"; + echo "Shell creation request sent."; + ' + networks: + - dataengine-network + + aas-web-ui: + image: eclipsebasyx/aas-gui:SNAPSHOT + container_name: aas-ui + volumes: + - ./logo:/usr/src/app/dist/Logo + environment: + AAS_REGISTRY_PATH: http://localhost:8080/shell-descriptors + SUBMODEL_REGISTRY_PATH: http://localhost:8080/submodel-descriptors + AAS_REPO_PATH: http://localhost:8080/shells + SUBMODEL_REPO_PATH: http://localhost:8080/submodels + CD_REPO_PATH: http://localhost:8080/concept-descriptions + BASE_PATH: "/aas-ui" + LOGO_PATH: "MM_Logo.svg" + PRIMARY_DARK_COLOR: "#EBECED" + PRIMARY_LIGHT_COLOR: "#00F2E5" + restart: always + depends_on: + template-repository: + condition: service_healthy + networks: + - dataengine-network + + mongo: + image: mongo:6.0 + container_name: mongo + environment: + MONGO_INITDB_ROOT_USERNAME: mongoAdmin + MONGO_INITDB_ROOT_PASSWORD: mongoPassword + networks: + - dataengine-network + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/logo/MM_Logo.svg b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/logo/MM_Logo.svg new file mode 100644 index 0000000..35fe73f --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/logo/MM_Logo.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/nginx/default.conf.template b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/nginx/default.conf.template new file mode 100644 index 0000000..7663fd9 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/nginx/default.conf.template @@ -0,0 +1,134 @@ +server { + listen 80; + server_name _; + + # Serve static files + location /fileprovider/ { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + root /usr/share/nginx/html; + } + + location /aas-ui/ { + proxy_pass http://aas-web-ui:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Authorization $http_authorization; + proxy_set_header Accept $http_accept; + proxy_set_header Content-Type $http_content_type; + } + + + location /shell-descriptors { + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /shells { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + #Change to TwinEngine when implemented + location /submodels { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /concept-descriptions/ { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://template-repository:8081; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /concept-descriptions { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://template-repository:8081; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /submodel-descriptors/ { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + } + + location /submodel-descriptors { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /serialization { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://twinengine-dataengine:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /description { + # Allow only GET and OPTIONS requests, deny all other methods + limit_except GET OPTIONS { + deny all; + } + proxy_pass http://sm-template-regisry:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/plugin/mock-metadata.json b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/plugin/mock-metadata.json new file mode 100644 index 0000000..4d15e7b --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/plugin/mock-metadata.json @@ -0,0 +1,38 @@ +[ + { + "globalAssetId": "https://mm-software.com/ids/assets/000-001", + "idShort": "M&M01", + "id": "https://mm-software.com/ids/aas/000-001", + "specificAssetIds": [], + "assetInformationData": { + "defaultThumbnail": { + "contentType": "image/jpeg", + "path": "https://cdn.pixabay.com/photo/2023/06/06/16/10/computer-8045026_1280.jpg" + } + } + }, + { + "globalAssetId": "https://mm-software.com/ids/assets/000-002", + "idShort": "M&M02", + "id": "https://mm-software.com/ids/aas/000-002", + "specificAssetIds": [], + "assetInformationData": { + "defaultThumbnail": { + "contentType": "image/jpeg", + "path": "https://cdn.pixabay.com/photo/2023/07/08/07/25/ai-generated-8113887_1280.jpg" + } + } + }, + { + "globalAssetId": "https://mm-software.com/ids/assets/000-003", + "idShort": "M&M03", + "id": "https://mm-software.com/ids/aas/000-003", + "specificAssetIds": [], + "assetInformationData": { + "defaultThumbnail": { + "contentType": "image/jpeg", + "path": "https://images.pexels.com/photos/27934787/pexels-photo-27934787.jpeg" + } + } + } +] diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/plugin/mock-submodel-data.json b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/plugin/mock-submodel-data.json new file mode 100644 index 0000000..1a5cdf4 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/plugin/mock-submodel-data.json @@ -0,0 +1,656 @@ +{ + "https://mm-software.com/submodel/000-001/ContactInformation": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation": [ + { + "0173-1#02-AAO204#003": "Manager001", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Acme Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Marketing" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "234-567-8901" + }, + "0173-1#02-AAO137#003": "Office", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + }, + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "345-678-9012" + }, + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Montags, 10-16 Uhr" + }, + "0173-1#02-AAO137#003": "Mobile" + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "john.doe@example.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Chat", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "8:00 AM to 6:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Bahnhofstraße 25" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "80335" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Bayern" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "John Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/more-info" + }, + { + "0173-1#02-AAO204#003": "Marketing Manager", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Acme Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Marketing" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "234-567-8901" + }, + "0173-1#02-AAO137#003": "Office", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + }, + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "345-678-9012" + }, + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Montags, 10-16 Uhr" + }, + "0173-1#02-AAO137#003": "Mobile" + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "john.doe@example.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Chat", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "8:00 AM to 6:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Bahnhofstraße 25" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "80335" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Bayern" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "John Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/more-info" + } + ] + } + }, + "https://mm-software.com/submodel/000-001/Nameplate": { + "https://admin-shell.io/idta/nameplate/3/0/Nameplate": { + "0112/2///61987#ABN590#002": "https://mm-software.com/Model-000/Serial-Nr-001", + "0112/2///61987#ABA565#009": { + "0112/2///61987#ABA565#009_de": "M&M" + }, + "0112/2///61987#ABP464#002": { + "0112/2///61987#ABP464#002_en": "M&MType ABC" + }, + "0112/2///61987#ABA581#007": "MM11-ABC22-001", + "0112/2///61987#ABB757#007": "2025-01-01", + "0112/2///61987#ABA567#009": { + "0112/2///61987#ABA567#009_en": "\u0022ABC-123\u0022" + }, + "0112/2///61987#ABA950#008": "FMABC1234", + "0112/2///61987#ABA951#009": "12345678", + "0112/2///61987#ABP000#002": "2022", + "0112/2///61360_7#AAS006#001": [ + { + "0112/2///61360_7#AAS009#001": [ + { + "0112/2///61987#ABA231#009": "0173-1#07-DAA603#004", + "0112/2///61987#ABH783#003": "KEMA99IECEX1105/128", + "0112/2///61987#ABO097#001": "2022-01-01", + "0112/2///61987#ABH830#002": "2022-01-01", + "0112/2///61987#ABO100#002": "file", + "0112/2///61987#ABB146#007": "0044" + }, + { + "0112/2///61987#ABA231#009": "0173-1#07-DAA603#004-2", + "0112/2///61987#ABH783#003": "KEMA99IECEX1105/128-2", + "0112/2///61987#ABO097#001": "2025-01-01", + "0112/2///61987#ABH830#002": "2025-01-01", + "0112/2///61987#ABO100#002": "file01", + "0112/2///61987#ABB146#007": "00100" + } + ] + } + ], + "0112/2///61987#ABP462#001": "DE", + "0112/2///61987#ABP463#001": "mm-logo", + "0173-1#02-ABI218#003/0173-1#01-AGZ672#004": [ + { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP_en": "M&MTestsample" + }, + "https://mm-software.com/AssetSpecificProperties/ArbitraryFile": "sampleFile", + "0173-1#02-ABI219#003/0173-1#01-AHD205#004": [ + { + "0173-1#01-AHD205#004": [ + { + "0173-1#02-AAO856#002": "", + "https://admin-shell.io/SMT/General/ArbitraryProp": "", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryFile": "TestFile", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP_en": "\u0022sample\u0022" + } + } + ] + } + ] + } + ] + } + }, + "https://mm-software.com/submodel/000-001/Reliability": { + "https://admin-shell.io/idta/iec62683/1/0/Reliability": { + "0112/2///62683#ACE006#001": "4", + "0112/2///62683#ACG071#001": [ + { + "0112/2///61987#ABA969#007": "DC", + "0112/2///61987#ABA588#004": "14.8", + "0112/2///61987#ABD461#004": "3.8", + "0112/2///61987#ABD462#004": "6.7", + "https://admin-shell.io/idta/FunctionalSafety/RatedOperationalCurrent/1/0": "78.9", + "0112/2///62683#ACE053#001": "TestType001", + "0112/2///62683#ACE070#001": "true", + "0112/2///62683#ACE055#001": "150", + "0112/2///62683#ACE054#001": "45" + } + ], + "0112/2///62683#ACG080#001": [ + { + "0112/2///62683#ACE061#001": "2", + "0112/2///62683#ACE062#001": "7", + "https://admin-shell.io/idta/Reliability/B10/1/0": "1" + } + ] + } + }, + "https://mm-software.com/submodel/000-002/ContactInformation": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation": [ + { + "0173-1#02-AAO204#003": "Manager001", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Acme Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Marketing" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "234-567-8901" + }, + "0173-1#02-AAO137#003": "Office", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + }, + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "345-678-9012" + }, + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Montags, 10-16 Uhr" + }, + "0173-1#02-AAO137#003": "Mobile" + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "john.doe@example.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Chat", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "8:00 AM to 6:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Bahnhofstraße 25" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "80335" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Bayern" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "John Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/more-info" + }, + { + "0173-1#02-AAO204#003": "Assistant Manager", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Beta Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Vertrieb" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "123-456-7890" + }, + "0173-1#02-AAO137#003": "assistant", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "jane.doe@betacorp.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info2", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Email", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "9:00 AM to 5:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Ringstraße 2" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "50667" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Nordrhein-Westfalen" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "Jane Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/info2" + } + ] + } + }, + "https://mm-software.com/submodel/000-002/Nameplate": { + "https://admin-shell.io/idta/nameplate/3/0/Nameplate": { + "0112/2///61987#ABA567#009": { + "0112/2///61987#ABA567#009_en": "\u0022ABC-123\u0022" + }, + "0112/2///61987#ABN590#002": "https://mm-software.com/Model-000/Serial-Nr-002", + "0112/2///61987#ABA565#009": { + "0112/2///61987#ABA565#009_de": "M&M" + }, + "0112/2///61987#ABP464#002": { + "0112/2///61987#ABP464#002_en": "M&MType ABC002" + }, + "0112/2///61987#ABA581#007": "MM11-ABC22-002", + "0112/2///61987#ABB757#007": "2024-01-01", + "0112/2///61987#ABA950#008": "FMABC1234", + "0112/2///61987#ABA951#009": "12345678", + "0112/2///61987#ABP000#002": "2022", + "0112/2///61360_7#AAS006#001": [ + { + "0112/2///61360_7#AAS009#001": [ + { + "0112/2///61987#ABA231#009": "0173-1#07-DAA603#004", + "0112/2///61987#ABH783#003": "KEMA99IECEX1105/128", + "0112/2///61987#ABO097#001": "2022-01-01", + "0112/2///61987#ABH830#002": "2022-01-01", + "0112/2///61987#ABO100#002": "file", + "0112/2///61987#ABB146#007": "0044" + } + ] + } + ], + "0112/2///61987#ABP462#001": "DE", + "0112/2///61987#ABP463#001": "mm-logo", + "0173-1#02-ABI218#003/0173-1#01-AGZ672#004": [ + { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP_en": "M&MTestsample" + }, + "https://mm-software.com/AssetSpecificProperties/ArbitraryFile": "sampleFile", + "0173-1#02-ABI219#003/0173-1#01-AHD205#004": [ + { + "0173-1#01-AHD205#004": [ + { + "0173-1#02-AAO856#002": "", + "https://admin-shell.io/SMT/General/ArbitraryProp": "", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryFile": "TestFile", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP_en": "\u0022sample\u0022" + } + } + ] + } + ] + } + ] + } + }, + "https://mm-software.com/submodel/000-002/Reliability": { + "https://admin-shell.io/idta/iec62683/1/0/Reliability": { + "0112/2///62683#ACE006#001": "4", + "0112/2///62683#ACG071#001": [ + { + "0112/2///61987#ABA969#007": "DC", + "0112/2///61987#ABA588#004": "14.8", + "0112/2///61987#ABD461#004": "3.8", + "0112/2///61987#ABD462#004": "6.7", + "https://admin-shell.io/idta/FunctionalSafety/RatedOperationalCurrent/1/0": "78.9", + "0112/2///62683#ACE053#001": "TestType002", + "0112/2///62683#ACE070#001": "true", + "0112/2///62683#ACE055#001": "150", + "0112/2///62683#ACE054#001": "45" + } + ], + "0112/2///62683#ACG080#001": [ + { + "0112/2///62683#ACE061#001": "2", + "0112/2///62683#ACE062#001": "7", + "https://admin-shell.io/idta/Reliability/B10/1/0": "1" + } + ] + } + }, + "https://mm-software.com/submodel/000-003/ContactInformation": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations": { + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation": [ + { + "0173-1#02-AAO204#003": "Product Manager0003", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Acme Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Marketing" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "234-567-8901" + }, + "0173-1#02-AAO137#003": "Office", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + }, + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "345-678-9012" + }, + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Montags, 10-16 Uhr" + }, + "0173-1#02-AAO137#003": "Mobile" + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "john.doe@example.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Chat", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "8:00 AM to 6:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Bahnhofstraße 25" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "80335" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Bayern" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "John Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/more-info" + }, + { + "0173-1#02-AAO204#003": "Manager", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Acme Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Marketing" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "234-567-8901" + }, + "0173-1#02-AAO137#003": "Office", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + }, + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "345-678-9012" + }, + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Montags, 10-16 Uhr" + }, + "0173-1#02-AAO137#003": "Mobile" + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "john.doe@example.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Chat", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "8:00 AM to 6:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Bahnhofstraße 25" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "80335" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Bayern" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "John Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/more-info" + }, + { + "0173-1#02-AAO204#003": "Assistant Manager", + "0173-1#02-AAW001#001": { + "0173-1#02-AAW001#001_en": "Beta Corporation" + }, + "0173-1#02-AAO127#003": { + "0173-1#02-AAO127#003_de": "Vertrieb" + }, + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/Phone": [ + { + "0173-1#02-AAO136#002": { + "0173-1#02-AAO136#002_en": "123-456-7890" + }, + "0173-1#02-AAO137#003": "assistant", + "https://mm-software.com/Phone/AvailableTime": { + "https://mm-software.com/Phone/AvailableTime_de": "Dienstags bis Donnerstags, 9-17 Uhr" + } + } + ], + "0173-1#02-AAQ836#005": [ + { + "0173-1#02-AAO198#002": "jane.doe@betacorp.com", + "0173-1#02-AAO199#003": "Work" + } + ], + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/": [ + { + "https://mm-software.com/ipCommunication/AddressOfAdditionalLink": "http://example.com/more-info2", + "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations/ContactInformation/IPCommunication/TypeOfCommunication": "Email", + "https://mm-software.com/IPCommunication/AvailableTime": { + "https://mm-software.com/IPCommunication/AvailableTime_de": "9:00 AM to 5:00 PM" + } + } + ], + "0173-1#02-AAO128#002": { + "0173-1#02-AAO128#002_de": "Ringstraße 2" + }, + "0173-1#02-AAO129#002": { + "0173-1#02-AAO129#002_de": "50667" + }, + "0173-1#02-AAO133#002": { + "0173-1#02-AAO133#002_de": "Nordrhein-Westfalen" + }, + "0173-1#02-AAO205#002": { + "0173-1#02-AAO205#002_en": "Jane Doe" + }, + "https://mm-software.com/AddressOfAdditionalLink": "http://example.com/info2" + } + ] + } + }, + "https://mm-software.com/submodel/000-003/Nameplate": { + "https://admin-shell.io/idta/nameplate/3/0/Nameplate": { + "0112/2///61987#ABA567#009": { + "0112/2///61987#ABA567#009_en": "\u0022ABC-123\u0022" + }, + "0112/2///61987#ABN590#002": "https://mm-software.com/Model-000/Serial-Nr-003", + "0112/2///61987#ABA565#009": { + "0112/2///61987#ABA565#009_de": "M&M" + }, + "0112/2///61987#ABP464#002": { + "0112/2///61987#ABP464#002_en": "M&MType ABC003" + }, + "0112/2///61987#ABA581#007": "MM11-ABC22-003", + "0112/2///61987#ABB757#007": "2025-01-01", + "0112/2///61360_7#AAS006#001": [ + { + "0112/2///61360_7#AAS009#001": [ + { + "0112/2///61987#ABA231#009": "0173-1#07-DAA603#004", + "0112/2///61987#ABH783#003": "KEMA99IECEX1105/128", + "0112/2///61987#ABO097#001": "2022-01-01", + "0112/2///61987#ABH830#002": "2022-01-01", + "0112/2///61987#ABO100#002": "file", + "0112/2///61987#ABB146#007": "0044" + } + ] + } + ], + "0112/2///61987#ABA950#008": "FMABC1234", + "0112/2///61987#ABA951#009": "12345678", + "0112/2///61987#ABP000#002": "2022", + "0112/2///61987#ABB757#007": "2022-01-01", + "0112/2///61987#ABP462#001": "DE", + "0112/2///61987#ABP463#001": "mm-logo", + "0173-1#02-ABI218#003/0173-1#01-AGZ672#004": [ + { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/AssetSpecificProperties/ArbitraryMLP_en": "\u0022sample\u0022" + }, + "https://mm-software.com/AssetSpecificProperties/ArbitraryFile": "sampleFile", + "0173-1#02-ABI219#003/0173-1#01-AHD205#004": [ + { + "0173-1#01-AHD205#004": [ + { + "0173-1#02-AAO856#002": "", + "https://admin-shell.io/SMT/General/ArbitraryProp": "", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryFile": "TestFile", + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP": { + "https://mm-software.com/GuidelineSpecificProperties/ArbitraryMLP_en": "\u0022sample\u0022" + } + } + ] + } + ] + } + ] + } + }, + "https://mm-software.com/submodel/000-003/Reliability": { + "https://admin-shell.io/idta/iec62683/1/0/Reliability": { + "0112/2///62683#ACE006#001": "4", + "0112/2///62683#ACG071#001": [ + { + "0112/2///61987#ABA969#007": "DC", + "0112/2///61987#ABA588#004": "14.8", + "0112/2///61987#ABD461#004": "3.8", + "0112/2///61987#ABD462#004": "6.7", + "https://admin-shell.io/idta/FunctionalSafety/RatedOperationalCurrent/1/0": "78.9", + "0112/2///62683#ACE053#001": "TestType003", + "0112/2///62683#ACE070#001": "true", + "0112/2///62683#ACE055#001": "150", + "0112/2///62683#ACE054#001": "45" + } + ], + "0112/2///62683#ACG080#001": [ + { + "0112/2///62683#ACE061#001": "2", + "0112/2///62683#ACE062#001": "7", + "https://admin-shell.io/idta/Reliability/B10/1/0": "1" + } + ] + } + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/DataAccess/Entity/MetaDataEntity.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/DataAccess/Entity/MetaDataEntity.cs new file mode 100644 index 0000000..872b04d --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/DataAccess/Entity/MetaDataEntity.cs @@ -0,0 +1,48 @@ +using System.Text.Json.Serialization; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.DataAccess.Entity; + +public class MetaDataEntity +{ + [JsonPropertyName("globalAssetId")] + public string GlobalAssetId { get; set; } = string.Empty; + + [JsonPropertyName("idShort")] + public string IdShort { get; set; } = string.Empty; + + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("specificAssetIds")] + public List? SpecificAssetIds { get; set; } + + [JsonPropertyName("assetInformationData")] + public AssetInformationDataEntity? AssetInformationData { get; set; } +} + +public class SpecificAssetIdEntity +{ + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("value")] + public string Value { get; set; } = string.Empty; +} + +public class AssetInformationDataEntity +{ + [JsonPropertyName("globalAssetId")] + public string? GlobalAssetId { get; set; } + + [JsonPropertyName("defaultThumbnail")] + public DefaultThumbnailDataEntity? DefaultThumbnail { get; set; } +} + +public class DefaultThumbnailDataEntity +{ + [JsonPropertyName("path")] + public string? Path { get; set; } + + [JsonPropertyName("contentType")] + public string? ContentType { get; set; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/DataAccess/MapperProfiles/AssetMappingProfile.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/DataAccess/MapperProfiles/AssetMappingProfile.cs new file mode 100644 index 0000000..682f97b --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/DataAccess/MapperProfiles/AssetMappingProfile.cs @@ -0,0 +1,23 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.DataAccess.Entity; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.DataAccess.MapperProfiles; + +public static class AssetMappingProfile +{ + public static AssetData ToDomainModel(this AssetInformationDataEntity entity, string globalAssetId, List? specificAssetIds) + { + return new AssetData + { + GlobalAssetId = globalAssetId, + SpecificAssetIds = specificAssetIds, + DefaultThumbnail = entity?.DefaultThumbnail == null + ? null + : new DefaultThumbnailData + { + ContentType = entity.DefaultThumbnail.ContentType, + Path = entity.DefaultThumbnail.Path + } + }; + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/DataAccess/MapperProfiles/ShellDescriptorMappingProfile.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/DataAccess/MapperProfiles/ShellDescriptorMappingProfile.cs new file mode 100644 index 0000000..001e525 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/DataAccess/MapperProfiles/ShellDescriptorMappingProfile.cs @@ -0,0 +1,25 @@ +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.DataAccess.Entity; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.DataAccess.MapperProfiles; + +public static class ShellDescriptorMappingProfile +{ + public static ShellDescriptorData MapToDomainModel(this MetaDataEntity entity) + { + return new ShellDescriptorData + { + Id = entity.Id, + GlobalAssetId = entity.GlobalAssetId, + IdShort = entity.IdShort, + SpecificAssetIds = entity.SpecificAssetIds?.Select(s => new SpecificAssetIdsData + { + Name = s.Name, + Value = s.Value + }).ToList() ?? [] + }; + } + + public static IList ToDomainModelList(this List dataList) + => dataList?.Select(MapToDomainModel).ToList() ?? []; +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/ManifestProvider/Config/Capabilities.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/ManifestProvider/Config/Capabilities.cs new file mode 100644 index 0000000..6ac2978 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/ManifestProvider/Config/Capabilities.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.Config; + +public class Capabilities +{ + public const string Section = "Capabilities"; + + [Required] + public bool HasShellDescriptor { get; set; } + + [Required] + public bool HasAssetInformation { get; set; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/ManifestProvider/Helper/JsonConverter.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/ManifestProvider/Helper/JsonConverter.cs new file mode 100644 index 0000000..d3d77ed --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/ManifestProvider/Helper/JsonConverter.cs @@ -0,0 +1,147 @@ +using System.Text.Json; + +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.ManifestProvider.Helper; + +public static class JsonConverter +{ + public static SemanticTreeNode ParseJson(JsonDocument doc) + { + ArgumentNullException.ThrowIfNull(doc); + + var root = doc.RootElement; + + if (root.ValueKind != JsonValueKind.String) + { + return ConvertJsonElement(root); + } + + var jsonString = root.GetString(); + using var nestedDoc = JsonDocument.Parse(jsonString!); + return ConvertJsonElement(nestedDoc.RootElement); + } + + private static SemanticTreeNode ConvertJsonElement(JsonElement element) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + { + var properties = element.EnumerateObject(); + var propertyCount = properties.Count(); + + switch (propertyCount) + { + case 0: + return new SemanticBranchNode(string.Empty, GetDataType(element)); + case 1: + { + var rootProperty = properties.First(); + var rootBranch = new SemanticBranchNode(rootProperty.Name, GetDataType(rootProperty.Value)); + ProcessJsonValue(rootProperty.Value, rootBranch); + return rootBranch; + } + } + + var root = new SemanticBranchNode(string.Empty, GetDataType(element)); + ProcessJsonObject(element, root); + return root; + } + case JsonValueKind.Array: + { + var syntheticRoot = new SemanticBranchNode(string.Empty, GetDataType(element)); + ProcessJsonArray(element, syntheticRoot); + return syntheticRoot; + } + default: + return new SemanticLeafNode(string.Empty, GetDataType(element), element.ToString()); + } + } + + private static void ProcessJsonValue(JsonElement valueElement, SemanticBranchNode parentBranch) + { + switch (valueElement.ValueKind) + { + case JsonValueKind.Object: + ProcessJsonObject(valueElement, parentBranch); + break; + + case JsonValueKind.Array: + ProcessJsonArray(valueElement, parentBranch); + break; + + default: + parentBranch.AddChild(new SemanticLeafNode( + parentBranch.SemanticId, + GetDataType(valueElement), + valueElement.ToString() + )); + break; + } + } + + private static void ProcessJsonObject(JsonElement objectElement, SemanticBranchNode parentBranch) + { + foreach (var property in objectElement.EnumerateObject()) + { + if (IsPrimitiveValue(property.Value)) + { + parentBranch.AddChild(new SemanticLeafNode( + property.Name, + GetDataType(property.Value), + property.Value.ToString() + )); + } + else if (property.Value.ValueKind == JsonValueKind.Array) + { + var baseSemanticId = property.Name; + ProcessJsonArray(property.Value, parentBranch, baseSemanticId); + } + else + { + var branch = new SemanticBranchNode(property.Name, GetDataType(property.Value)); + ProcessJsonValue(property.Value, branch); + parentBranch.AddChild(branch); + } + } + } + + private static void ProcessJsonArray(JsonElement arrayElement, SemanticBranchNode parentBranch, string? baseSemanticId = null) + { + foreach (var item in arrayElement.EnumerateArray()) + { + var semanticId = baseSemanticId ?? parentBranch.SemanticId; + var arrayItemBranch = new SemanticBranchNode(semanticId, GetDataType(item)); + ProcessJsonValue(item, arrayItemBranch); + parentBranch.AddChild(arrayItemBranch); + } + } + + private static bool IsPrimitiveValue(JsonElement element) + { + return element.ValueKind switch + { + JsonValueKind.String or + JsonValueKind.Number or + JsonValueKind.True or + JsonValueKind.False or + JsonValueKind.Null => true, + _ => false + }; + } + + private static DataType GetDataType(JsonElement element) => + element.ValueKind switch + { + JsonValueKind.Object => DataType.Object, + JsonValueKind.Array => DataType.Array, + JsonValueKind.String => DataType.String, + JsonValueKind.Number => DataType.Number, + JsonValueKind.True => DataType.Boolean, + JsonValueKind.False => DataType.Boolean, + JsonValueKind.Null => DataType.Null, + JsonValueKind.Undefined => DataType.Unknown, + _ => DataType.Unknown + }; +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/ManifestProvider/ManifestProvider.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/ManifestProvider/ManifestProvider.cs new file mode 100644 index 0000000..40965be --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/ManifestProvider/ManifestProvider.cs @@ -0,0 +1,60 @@ +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Manifest; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Manifest; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.Config; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.ManifestProvider.Helper; + +using Microsoft.Extensions.Options; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.ManifestProvider; + +public class ManifestProvider( + ILogger logger, + IOptions capabilities) : IManifestProvider +{ + private readonly bool _hasShellDescriptor = capabilities.Value.HasShellDescriptor; + private readonly bool _hasAssetInformation = capabilities.Value.HasAssetInformation; + + public ManifestData GetManifestData() + { + logger.LogInformation("Starting getting manifest data"); + + var semanticTreeNode = JsonConverter.ParseJson(MockData.SubmodelData); + + var supportedSemanticIds = GetLeafSemanticIds(semanticTreeNode); + + var manifestData = new ManifestData + { + SupportedSemanticIds = supportedSemanticIds, + Capabilities = new CapabilitiesData { HasAssetInformation = _hasAssetInformation, HasShellDescriptor = _hasShellDescriptor } + }; + return manifestData; + } + + private static IList GetLeafSemanticIds(SemanticTreeNode node) + { + var semanticIds = new List(); + + CollectLeafSemanticIds(node, semanticIds); + return semanticIds.Distinct(StringComparer.Ordinal).ToList(); + } + + private static void CollectLeafSemanticIds(SemanticTreeNode node, List supportedSemanticId) + { + if (node is SemanticLeafNode leaf) + { + supportedSemanticId.Add(leaf.SemanticId); + return; + } + + if (node is not SemanticBranchNode branch) + { + return; + } + + foreach (var child in branch.Children) + { + CollectLeafSemanticIds(child, supportedSemanticId); + } + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MetaDataProvider/Helper/Paginator.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MetaDataProvider/Helper/Paginator.cs new file mode 100644 index 0000000..f6faec2 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MetaDataProvider/Helper/Paginator.cs @@ -0,0 +1,39 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Common.Extensions; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.MetaDataProvider.Helper; + +public static class Paginator +{ + public static (IList Items, PagingMetaData PagingMetaData) GetPagedResult( + IList allItems, + Func getId, + int? limit, + string? cursor) + { + var startIndex = 0; + if (!string.IsNullOrEmpty(cursor)) + { + var lastId = cursor.DecodeBase64(); + startIndex = allItems.ToList().FindIndex(item => getId(item) == lastId) + 1; + } + + var pageSize = limit ?? 100; + var pagedItems = allItems.Skip(startIndex).Take(pageSize).ToList(); + + string? nextCursor = null; + + if (limit == null && cursor == null && pagedItems.Count < pageSize) + { + return (pagedItems, new PagingMetaData { Cursor = nextCursor }); + } + + var lastItem = pagedItems.LastOrDefault(); + if (lastItem != null) + { + nextCursor = getId(lastItem).EncodeToBase64(); + } + + return (pagedItems, new PagingMetaData { Cursor = nextCursor }); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MetaDataProvider/MetaDataProvider.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MetaDataProvider/MetaDataProvider.cs new file mode 100644 index 0000000..4ccaf89 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MetaDataProvider/MetaDataProvider.cs @@ -0,0 +1,99 @@ +using System.Text.Json; + +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.MetaData; +using AAS.TwinEngine.Plugin.TestPlugin.Common.Extensions; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.MetaData; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.DataAccess.Entity; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.DataAccess.MapperProfiles; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.MetaDataProvider.Helper; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.MetaDataProvider; + +public class MetaDataProvider : IMetaDataProvider +{ + private readonly ILogger _logger; + private readonly Dictionary _shellDescriptorLookup = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _assetLookup = new(StringComparer.OrdinalIgnoreCase); + private readonly JsonSerializerOptions _jsonSerializerOptions = new() { PropertyNameCaseInsensitive = true }; + + public MetaDataProvider(ILogger logger) + { + _logger = logger; + BuildDictionaries(MockData.MetaData); + } + + private void BuildDictionaries(JsonDocument jsonData) + { + var entities = jsonData.Deserialize>(_jsonSerializerOptions) ?? []; + + foreach (var entity in entities.Where(e => !string.IsNullOrEmpty(e.Id))) + { + _shellDescriptorLookup[entity.Id] = entity; + + if (entity.AssetInformationData is not null) + { + _assetLookup[entity.Id] = MapToDomainModel(entity); + } + } + + _logger.LogInformation("Loaded {Count} shell-descriptors entries", _shellDescriptorLookup.Count); + _logger.LogInformation("Loaded {Count} asset entries", _assetLookup.Count); + } + + private static AssetData MapToDomainModel(MetaDataEntity entity) + { + return entity.AssetInformationData!.ToDomainModel( + entity.GlobalAssetId, + entity.SpecificAssetIds?.Select(x => new SpecificAssetIdsData + { + Name = x.Name, + Value = x.Value + }).ToList() + ); + } + + public Task GetShellDescriptorsAsync(int? limit, string? cursor, CancellationToken cancellationToken) + { + var domainModels = _shellDescriptorLookup.Values.ToList(); + + var shellDescriptors = domainModels.ToDomainModelList(); + + if (cursor == null || _shellDescriptorLookup.TryGetValue(cursor.DecodeBase64(), out _)) + { + var (pagedItems, pagingMeta) = Paginator.GetPagedResult(shellDescriptors, s => s.Id!, limit, cursor); + + return Task.FromResult(new ShellDescriptorsData() + { + PagingMetaData = pagingMeta, + Result = pagedItems + }); + } + + _logger.LogWarning("Invalid cursor provided."); + throw new NotFoundException(ExceptionMessages.ShellDescriptorDataNotFound); + } + + public Task GetShellDescriptorAsync(string aasIdentifier, CancellationToken cancellationToken) + { + if (_shellDescriptorLookup.TryGetValue(aasIdentifier, out var entity)) + { + return Task.FromResult(entity.MapToDomainModel()); + } + + _logger.LogWarning("Shell-descriptors not found for ID: {AasIdentifier}", aasIdentifier); + throw new NotFoundException(ExceptionMessages.ShellDescriptorDataNotFound); + } + + public Task GetAssetAsync(string shellIdentifier, CancellationToken cancellationToken) + { + if (_assetLookup.TryGetValue(shellIdentifier, out var assetInformation)) + { + return Task.FromResult(assetInformation); + } + + _logger.LogWarning("Asset not found for ID: {ShellIdentifier}", shellIdentifier); + throw new NotFoundException(ExceptionMessages.AssetNotFound); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MockData.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MockData.cs new file mode 100644 index 0000000..fa844b8 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MockData.cs @@ -0,0 +1,9 @@ +using System.Text.Json; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers; + +public record MockData +{ + public static JsonDocument MetaData { get; set; } + public static JsonDocument SubmodelData { get; set; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MockDataInitializer.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MockDataInitializer.cs new file mode 100644 index 0000000..6229031 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/MockDataInitializer.cs @@ -0,0 +1,45 @@ +using System.Text.Json; + +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Constants; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers; + +public class MockDataInitializer(IHostEnvironment env, ILogger logger) +{ + public void Initialize(CancellationToken cancellationToken) + { + var dataPath = Path.Combine(env.ContentRootPath, "Data"); + + MockData.MetaData = LoadData(Path.Combine(dataPath, "mock-metadata.json")); + + MockData.SubmodelData = LoadData(Path.Combine(dataPath, "mock-submodel-data.json")); + } + + private JsonDocument LoadData(string filePath) + { + if (!File.Exists(filePath)) + { + logger.LogCritical("data file not found at {FilePath}", filePath); + throw new FileNotFoundException("JSON data file not found.", filePath); + } + + try + { + using var fileStream = File.OpenRead(filePath); + using var streamReader = new StreamReader(fileStream); + var jsonContent = streamReader.ReadToEnd(); + return JsonDocument.Parse(jsonContent); + } + catch (JsonException jex) + { + logger.LogError(jex, "Invalid JSON in file {FilePath}", filePath); + throw new InternalServerException(ExceptionMessages.ResourceNotValid); + } + catch (Exception ex) + { + logger.LogError(ex, "Error reading data file {FilePath}", filePath); + throw new InternalServerException(ExceptionMessages.ResourceNotValid); + } + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/SubmodelProviders/Helper/JsonNodeInfo.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/SubmodelProviders/Helper/JsonNodeInfo.cs new file mode 100644 index 0000000..07636c4 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/SubmodelProviders/Helper/JsonNodeInfo.cs @@ -0,0 +1,10 @@ +using System.Text.Json; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.SubmodelProviders.Helper; + +public class JsonNodeInfo +{ + public JsonValueKind Kind { get; set; } + public int? ArrayLength { get; set; } + public string? StringValue { get; set; } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/SubmodelProviders/SubmodelProvider.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/SubmodelProviders/SubmodelProvider.cs new file mode 100644 index 0000000..75ee251 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Infrastructure/Providers/SubmodelProviders/SubmodelProvider.cs @@ -0,0 +1,274 @@ +using System.Text.Json; + +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel.Config; +using AAS.TwinEngine.Plugin.TestPlugin.DomainModel.Submodel; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.SubmodelProviders.Helper; + +using Microsoft.Extensions.Options; + +namespace AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.SubmodelProviders; + +public class SubmodelProvider : ISubmodelProvider +{ + private readonly ILogger _logger; + private readonly string _submodelElementIndexContextPrefix; + private Dictionary> _nodeInfoDictionaries; + + public SubmodelProvider( + ILogger logger, + IOptions semantics) + { + _logger = logger; + _submodelElementIndexContextPrefix = semantics.Value.IndexContextPrefix; + _nodeInfoDictionaries = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + PrecomputeDictionaries(); + } + + private void PrecomputeDictionaries() + { + var rootElement = MockData.SubmodelData.RootElement; + _nodeInfoDictionaries = rootElement + .EnumerateObject() + .Where(p => p.Value.ValueKind == JsonValueKind.Object) + .ToDictionary(keySelector: p => p.Name, + elementSelector: BuildSubmodelDictionary, + comparer: StringComparer.OrdinalIgnoreCase); + } + + private static Dictionary BuildSubmodelDictionary(JsonProperty product) + { + var semanticDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var property in product.Value.EnumerateObject()) + { + BuildDictionaries(property.Value, property.Name, semanticDictionary); + } + + return semanticDictionary; + } + + private static void BuildDictionaries(JsonElement element, string currentPath, Dictionary dictionary) + { + var nodeInfo = new JsonNodeInfo + { + Kind = element.ValueKind + }; + + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + var newPath = string.IsNullOrEmpty(currentPath) + ? property.Name + : $"{currentPath}.{property.Name}"; + BuildDictionaries(property.Value, newPath, dictionary); + } + + break; + + case JsonValueKind.Array: + nodeInfo.ArrayLength = element.GetArrayLength(); + for (var i = 0; i < element.GetArrayLength(); i++) + { + var newPath = $"{currentPath}.{i}"; + BuildDictionaries(element[i], newPath, dictionary); + } + + break; + + case JsonValueKind.String: + nodeInfo.StringValue = element.GetString()!; + break; + } + + dictionary[currentPath] = nodeInfo; + } + + public SemanticTreeNode EnrichWithData(SemanticTreeNode semanticTreeNode, string submodelId) + { + _logger.LogInformation("Starting semantic tree enrichment for submodelId {SubmodelId}", submodelId); + + if (!_nodeInfoDictionaries.TryGetValue(submodelId, out var nodeInfoDictionary)) + { + _logger.LogError("Submodel identifier {SubmodelId} not found", submodelId); + throw new NotFoundException(); + } + + var result = FillData(semanticTreeNode, [], nodeInfoDictionary); + RemoveIndexPrefix(result); + + _logger.LogInformation("Completed tree enrichment"); + + return result; + } + + private SemanticTreeNode FillData(SemanticTreeNode node, List semanticPath, Dictionary nodeInfoDictionary) + { + var newSemanticPath = new List(semanticPath) { node.SemanticId }; + + switch (node) + { + case SemanticLeafNode leaf: + return HandleLeafNode(leaf, newSemanticPath, nodeInfoDictionary); + case SemanticBranchNode branch: + return HandleBranchNode(branch, newSemanticPath, nodeInfoDictionary); + default: + _logger.LogError("Unknown node type: {Type}", node.GetType().Name); + throw new ArgumentException("Invalid node type"); + } + } + + private string CreateDictionaryKey(List semanticPath) + { + var keyParts = new List(); + + foreach (var id in semanticPath) + { + if (id.Contains(_submodelElementIndexContextPrefix, StringComparison.Ordinal)) + { + var parts = id.Split(_submodelElementIndexContextPrefix); + if (parts.Length == 2 && int.TryParse(parts[1], out var index)) + { + keyParts.Add(parts[0]); + keyParts.Add(index.ToString()); + continue; + } + } + + keyParts.Add(id); + } + + return string.Join('.', keyParts); + } + + private SemanticTreeNode HandleBranchNode(SemanticBranchNode branch, List semanticPath, Dictionary nodeInfoDictionary) + { + var dictKey = CreateDictionaryKey(semanticPath); + + if (!nodeInfoDictionary.TryGetValue(dictKey, out var nodeInfo)) + { + _logger.LogWarning("Path not found in structure: {Path}", dictKey); + throw new NotFoundException($"Value Not found or given Element : {dictKey.LastOrDefault()}"); + } + + switch (nodeInfo.Kind) + { + case JsonValueKind.Array: + var clones = CreateArrayNode(branch, dictKey, nodeInfo.ArrayLength, semanticPath, nodeInfoDictionary); + branch.ReplaceChildren(clones); + break; + case JsonValueKind.Object: + ProcessChildNodes(branch, semanticPath, nodeInfoDictionary); + break; + default: + _logger.LogWarning("Unexpected JSON type {Type} for branch", nodeInfo.Kind); + break; + } + + return branch; + } + + private void ProcessChildNodes(SemanticBranchNode branch, List semanticPath, Dictionary nodeInfoDictionary) + { + var newChildren = new List(); + + foreach (var child in branch.Children) + { + var childPath = new List(semanticPath) { child.SemanticId }; + var childKey = CreateDictionaryKey(childPath); + + if (nodeInfoDictionary.TryGetValue(childKey, out var childInfo) && + childInfo.Kind == JsonValueKind.Array && + child is SemanticBranchNode childBranch) + { + newChildren.AddRange(CreateArrayNode(childBranch, childKey, childInfo.ArrayLength, semanticPath, nodeInfoDictionary)); + } + else + { + var nodes = FillData(child, semanticPath, nodeInfoDictionary); + newChildren.Add(nodes); + } + } + + branch.ReplaceChildren(newChildren); + } + + private List CreateArrayNode(SemanticBranchNode node, string dictKey, int? arrayLength, List parentPath, Dictionary nodeInfoDictionary) + { + if (arrayLength is not > 0) + { + _logger.LogWarning("Invalid array length for {Key}", dictKey); + return []; + } + + var clones = new List(); + for (var i = 0; i < arrayLength.Value; i++) + { + var clone = CloneNode(node) as SemanticBranchNode; + clone!.SemanticId = $"{node.SemanticId}{_submodelElementIndexContextPrefix}{i:D2}"; + + var processedClone = FillData(clone, parentPath, nodeInfoDictionary); + clones.Add(processedClone); + } + + return clones; + } + + private SemanticTreeNode HandleLeafNode(SemanticLeafNode leaf, List semanticPath, Dictionary nodeInfoDictionary) + { + var dictKey = CreateDictionaryKey(semanticPath); + + leaf.Value = nodeInfoDictionary.TryGetValue(dictKey, out var nodeInfo) && nodeInfo.StringValue != null + ? nodeInfo.StringValue + : string.Empty; + return leaf; + } + + private static SemanticTreeNode CloneNode(SemanticTreeNode node) + { + switch (node) + { + case SemanticLeafNode leaf: + return new SemanticLeafNode(leaf.SemanticId, leaf.DataType, leaf.Value); + case SemanticBranchNode branch: + { + var cloned = new SemanticBranchNode(branch.SemanticId, branch.DataType); + foreach (var child in branch.Children) + { + var clonedChild = CloneNode(child); + cloned.AddChild(clonedChild); + } + + return cloned; + } + default: + throw new InvalidOperationException("Unknown node type"); + } + } + + private void RemoveIndexPrefix(SemanticTreeNode node) + { + var id = node.SemanticId; + var idx = id.IndexOf(_submodelElementIndexContextPrefix, StringComparison.Ordinal); + if (idx >= 0) + { + node.SemanticId = id[..idx]; + } + + if (node is not SemanticBranchNode branch) + { + return; + } + + foreach (var child in from child in branch.Children + where node is SemanticBranchNode + select child) + { + RemoveIndexPrefix(child); + } + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Program.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/Program.cs new file mode 100644 index 0000000..fa8b263 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Program.cs @@ -0,0 +1,59 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers; +using AAS.TwinEngine.Plugin.TestPlugin.ServiceConfiguration; + +using Asp.Versioning; + +namespace AAS.TwinEngine.Plugin.TestPlugin; + +public static class Program +{ + private static readonly Version ApiVersion = new(1, 0); + private const string ApiTitle = "TestPlugin API"; + + public static async Task Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + builder.ConfigureLogging(builder.Configuration); + + builder.Services.AddHttpContextAccessor(); + builder.Services.ConfigureInfrastructure(builder.Configuration); + builder.Services.ConfigureApplication(builder.Configuration); + builder.Services.AddAuthorization(); + + builder.Services.AddControllers(); + + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddOpenApiDocument(settings => + { + settings.DocumentName = ApiVersion.ToString(); + settings.Title = ApiTitle; + }); + + builder.Services.AddApiVersioning(options => + { + options.DefaultApiVersion = new ApiVersion(ApiVersion.Major, ApiVersion.Minor); + options.AssumeDefaultVersionWhenUnspecified = true; + options.ReportApiVersions = true; + options.ApiVersionReader = new HeaderApiVersionReader("api-version"); + }) + .AddMvc(); + + var app = builder.Build(); + + using (var scope = app.Services.CreateScope()) + { + var initializer = scope.ServiceProvider.GetRequiredService(); + initializer.Initialize(CancellationToken.None); + } + + app.UseExceptionHandler(); + app.UseHttpsRedirection(); + app.UseAuthorization(); + app.UseOpenApi(c => c.PostProcess = (d, _) => d.Servers.Clear()); + app.UseSwaggerUI(c => c.SwaggerEndpoint($"/swagger/{ApiVersion:F1}/swagger.json", ApiTitle)); + app.MapControllers(); + + await app.RunAsync(); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Properties/launchSettings.json b/source/AAS.TwinEngine.Plugin.TestPlugin/Properties/launchSettings.json new file mode 100644 index 0000000..10725d6 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Properties/launchSettings.json @@ -0,0 +1,39 @@ +{ + "profiles": { + "AAS.TwinEngine.Plugin.TestPlugin": { + "commandName": "Project", + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:5057" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": false, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": false + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:13390", + "sslPort": 0 + } + } +} \ No newline at end of file diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/ApplicationDependencyInjectionExtensions.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/ApplicationDependencyInjectionExtensions.cs new file mode 100644 index 0000000..c4bf7cb --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/ApplicationDependencyInjectionExtensions.cs @@ -0,0 +1,33 @@ +using AAS.TwinEngine.Plugin.TestPlugin.Api.Manifest.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.MetaData.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Handler; +using AAS.TwinEngine.Plugin.TestPlugin.Api.Submodel.Services; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Exceptions; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Manifest; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.MetaData; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel; + +using FluentValidation; +namespace AAS.TwinEngine.Plugin.TestPlugin.ServiceConfiguration; + +public static class ApplicationDependencyInjectionExtensions +{ + public static void ConfigureApplication(this IServiceCollection services, IConfiguration configuration) + { + services.AddValidatorsFromAssembly(typeof(ApplicationDependencyInjectionExtensions).Assembly); + services.AddExceptionHandler(); + services.AddProblemDetails(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/Config/OpenTelemetrySettings.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/Config/OpenTelemetrySettings.cs new file mode 100644 index 0000000..badf4b9 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/Config/OpenTelemetrySettings.cs @@ -0,0 +1,9 @@ +namespace AAS.TwinEngine.Plugin.TestPlugin.ServiceConfiguration.Config; + +public class OpenTelemetrySettings +{ + public const string Section = "OpenTelemetry"; + public string OtlpEndpoint { get; set; } = "http://localhost:4317"; + public string ServiceName { get; set; } = "TestPlugin"; + public string ServiceVersion { get; set; } = "1.0.0"; +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/InfrastructureDependencyInjectionExtensions.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/InfrastructureDependencyInjectionExtensions.cs new file mode 100644 index 0000000..63a326b --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/InfrastructureDependencyInjectionExtensions.cs @@ -0,0 +1,24 @@ +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Manifest; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.MetaData; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel; +using AAS.TwinEngine.Plugin.TestPlugin.ApplicationLogic.Services.Submodel.Config; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.Config; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.ManifestProvider; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.MetaDataProvider; +using AAS.TwinEngine.Plugin.TestPlugin.Infrastructure.Providers.SubmodelProviders; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ServiceConfiguration; + +public static class InfrastructureDependencyInjectionExtensions +{ + public static void ConfigureInfrastructure(this IServiceCollection services, IConfiguration configuration) + { + services.AddOptions().Bind(configuration.GetSection(Semantics.Section)).ValidateDataAnnotations().ValidateOnStart(); + services.AddOptions().Bind(configuration.GetSection(Capabilities.Section)).ValidateDataAnnotations().ValidateOnStart(); + services.AddScoped(); + services.AddSingleton(); + services.AddSingleton(); + services.AddScoped(); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/LoggingConfigurationExtension.cs b/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/LoggingConfigurationExtension.cs new file mode 100644 index 0000000..eea58b0 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/ServiceConfiguration/LoggingConfigurationExtension.cs @@ -0,0 +1,65 @@ +using System.Diagnostics.CodeAnalysis; + +using AAS.TwinEngine.Plugin.TestPlugin.ServiceConfiguration.Config; + +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; + +using Serilog; +using Serilog.Core; +using Serilog.Events; + +namespace AAS.TwinEngine.Plugin.TestPlugin.ServiceConfiguration; + +[ExcludeFromCodeCoverage] +internal static class LoggingConfigurationExtension +{ + public static void ConfigureLogging(this WebApplicationBuilder builder, IConfiguration configuration) + { + var otelSettings = configuration.GetSection(OpenTelemetrySettings.Section).Get() ?? new OpenTelemetrySettings(); + var logLevelSwitch = new LoggingLevelSwitch(LogEventLevel.Information); + + _ = builder.Services.AddSingleton(logLevelSwitch); + + _ = builder.Host.UseSerilog((context, loggerConfig) => + { + loggerConfig + .ReadFrom.Configuration(context.Configuration) + .Enrich.FromLogContext() + .MinimumLevel.ControlledBy(logLevelSwitch); + }, writeToProviders: true); + + _ = builder.Logging.ClearProviders(); + + _ = builder.Logging.AddOpenTelemetry(options => + { + options.IncludeScopes = true; + options.IncludeFormattedMessage = true; + options.ParseStateValues = true; + _ = options.AddOtlpExporter(otlp => otlp.Endpoint = new Uri(otelSettings.OtlpEndpoint)); + }); + + _ = builder.Services.AddOpenTelemetry() + .ConfigureResource(resourceConfig => resourceConfig + .AddService( + serviceName: otelSettings.ServiceName, + serviceVersion: otelSettings.ServiceVersion, + serviceInstanceId: Environment.MachineName)) + .WithTracing(tracerProvider => + { + _ = tracerProvider + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddOtlpExporter(otlp => otlp.Endpoint = new Uri(otelSettings.OtlpEndpoint)); + }) + .WithMetrics(metricsProvider => + { + _ = metricsProvider + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddOtlpExporter(otlp => otlp.Endpoint = new Uri(otelSettings.OtlpEndpoint)); + }); + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/appsettings.Development.json b/source/AAS.TwinEngine.Plugin.TestPlugin/appsettings.Development.json new file mode 100644 index 0000000..f678d69 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/appsettings.Development.json @@ -0,0 +1,56 @@ +{ + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Semantics": { + "IndexContextPrefix": "_aastwinengineindex_" + }, + "Capabilities": { + "HasShellDescriptor": true, + "HasAssetInformation": true + }, + "AllowedHosts": "*", + "OpenTelemetry": { + "OtlpEndpoint": "http://localhost:4317", + "ServiceName": "TestPlugin", + "ServiceVersion": "1.0.0." + }, + "Serilog": { + "Using": [ + "Serilog.Sinks.Console" + ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Error" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] : {Message} (CorrelationId: {CorrelationId}) (User: {User}) (SourceContext: {SourceContext}) {Details}{NewLine}{Exception}", + "restrictedToMinimumLevel": "Verbose" + } + }, + { + "Name": "File", + "Args": { + "path": "logs/TestPlugin-.log", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}" + } + } + ], + "Enrich": [ "FromLogContext", "WithThreadId" ], + "Properties": { + "Application": "TestPlugin" + } + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/appsettings.json b/source/AAS.TwinEngine.Plugin.TestPlugin/appsettings.json new file mode 100644 index 0000000..c042643 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/appsettings.json @@ -0,0 +1,55 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Semantics": { + "IndexContextPrefix": "" + }, + "Capabilities": { + "HasShellDescriptor": true, + "HasAssetInformation": true + }, + "AllowedHosts": "*", + "OpenTelemetry": { + "OtlpEndpoint": "http://localhost:4317", + "ServiceName": "TestPlugin", + "ServiceVersion": "1.0.0." + }, + "Serilog": { + "Using": [ + "Serilog.Sinks.Console" + ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Error" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] : {Message} (CorrelationId: {CorrelationId}) (User: {User}) (SourceContext: {SourceContext}) {Details}{NewLine}{Exception}", + "restrictedToMinimumLevel": "Verbose" + } + }, + { + "Name": "File", + "Args": { + "path": "logs/TestPlugin-.log", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}" + } + } + ], + "Enrich": [ "FromLogContext", "WithThreadId" ], + "Properties": { + "Application": "DataEngine" + } + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/config.nswag b/source/AAS.TwinEngine.Plugin.TestPlugin/config.nswag new file mode 100644 index 0000000..bba8432 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/config.nswag @@ -0,0 +1,64 @@ +{ + "runtime": "Net80", + "defaultVariables": "Output=v1.json,Configuration=Debug,Version=1.0", + "documentGenerator": { + "aspNetCoreToOpenApi": { + "project": "AAS.TwinEngine.Plugin.TestPlugin.csproj", + "msBuildProjectExtensionsPath": null, + "configuration": "$(Configuration)", + "runtime": null, + "targetFramework": null, + "noBuild": true, + "msBuildOutputPath": null, + "verbose": true, + "workingDirectory": null, + "requireParametersWithoutDefault": true, + "defaultPropertyNameHandling": "Default", + "defaultReferenceTypeNullHandling": "Null", + "defaultDictionaryValueReferenceTypeNullHandling": "NotNull", + "defaultResponseReferenceTypeNullHandling": "NotNull", + "generateOriginalParameterNames": true, + "defaultEnumHandling": "Integer", + "flattenInheritanceHierarchy": false, + "generateKnownTypes": true, + "generateEnumMappingDescription": false, + "generateXmlObjects": false, + "generateAbstractProperties": false, + "generateAbstractSchemas": true, + "generateResponseTypes": true, + "ignoreObsoleteProperties": false, + "allowReferencesWithProperties": false, + "useXmlDocumentation": true, + "resolveExternalXmlDocumentation": true, + "excludedTypeNames": [], + "serviceHost": null, + "serviceBasePath": null, + "serviceSchemes": [], + "infoTitle": "$(Version)", + "infoDescription": null, + "infoVersion": "$(Version)", + "documentTemplate": null, + "documentProcessorTypes": [], + "operationProcessorTypes": [], + "typeNameGeneratorType": null, + "schemaNameGeneratorType": null, + "contractResolverType": null, + "serializerSettingsType": null, + "useDocumentProvider": true, + "documentName": "$(Version)", + "aspNetCoreEnvironment": null, + "createWebHostBuilderMethod": null, + "startupType": null, + "allowNullableBodyParameters": true, + "useHttpAttributeNameAsOperationId": false, + "output": "$(Output)", + "outputType": "OpenApi3", + "newLineBehavior": "Auto", + "assemblyPaths": [], + "assemblyConfig": null, + "referencePaths": [], + "useNuGetCache": false + } + }, + "codeGenerators": {} +}