diff --git a/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/ConnectedSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/ConnectedSubmodelRepository.java index 397767fa6..376eaf2e7 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/ConnectedSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/ConnectedSubmodelRepository.java @@ -217,12 +217,16 @@ public OperationVariable[] invokeOperation(String submodelId, String idShortPath @Override public SubmodelValueOnly getSubmodelByIdValueOnly(String submodelId) throws ElementDoesNotExistException { - return new SubmodelValueOnly(getSubmodelByIdMetadata(submodelId).getSubmodelElements()); + return new SubmodelValueOnly(getSubmodel(submodelId).getSubmodelElements()); } @Override public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNotExistException { - return repoApi.getSubmodelById(submodelId, null, null); + try { + return repoApi.getSubmodelByIdMetadata(submodelId, null); + } catch (ApiException e) { + throw mapExceptionSubmodelAccess(submodelId, e); + } } @Override diff --git a/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/internal/SubmodelRepositoryApi.java b/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/internal/SubmodelRepositoryApi.java index 5ed4fb1a8..c82cadd29 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/internal/SubmodelRepositoryApi.java +++ b/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/internal/SubmodelRepositoryApi.java @@ -32,6 +32,7 @@ import java.net.http.HttpResponse; import java.time.Duration; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.StringJoiner; import java.util.function.Consumer; @@ -238,6 +239,123 @@ private HttpRequest.Builder getSubmodelByIdRequestBuilder(String submodelIdentif return localVarRequestBuilder; } + /** + * Returns the metadata attributes of a specific Submodel + * + * @param submodelIdentifier + * The Submodel’s unique id (UTF8-BASE64-URL-encoded) (required) + * @param level + * Determines the structural depth of the respective resource content + * (optional, default to deep) + * @return SubmodelMetadata + * @throws ApiException + * if fails to make API call + */ + public Submodel getSubmodelByIdMetadata(String submodelIdentifier, String level) throws ApiException { + + ApiResponse localVarResponse = getSubmodelByIdMetadataWithHttpInfo(submodelIdentifier, level); + return localVarResponse.getData(); + } + + /** + * Returns the metadata attributes of a specific Submodel + * + * @param submodelIdentifier + * The Submodel’s unique id (UTF8-BASE64-URL-encoded) (required) + * @param level + * Determines the structural depth of the respective resource content + * (optional, default to deep) + * @return ApiResponse<SubmodelMetadata> + * @throws ApiException + * if fails to make API call + */ + public ApiResponse getSubmodelByIdMetadataWithHttpInfo(String submodelIdentifier, String level) throws ApiException { + String submodelIdentifierAsBytes = ApiClient.base64UrlEncode(submodelIdentifier); + return getSubmodelByIdMetadataWithHttpInfoNoUrlEncoding(submodelIdentifierAsBytes, level); + + } + + /** + * Returns the metadata attributes of a specific Submodel + * + * @param submodelIdentifier + * The Submodel’s unique id (UTF8-BASE64-URL-encoded) (required) + * @param level + * Determines the structural depth of the respective resource content + * (optional, default to deep) + * @return ApiResponse<SubmodelMetadata> + * @throws ApiException + * if fails to make API call + */ + public ApiResponse getSubmodelByIdMetadataWithHttpInfoNoUrlEncoding(String submodelIdentifier, String level) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = getSubmodelByIdMetadataRequestBuilder(submodelIdentifier, level); + try { + HttpResponse localVarResponse = memberVarHttpClient.send(localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("getSubmodelByIdMetadata", localVarResponse); + } + Submodel deserializedSubmodel = localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() { + }); + + if (deserializedSubmodel != null && deserializedSubmodel.getSubmodelElements() != null && deserializedSubmodel.getSubmodelElements().isEmpty()) + deserializedSubmodel.setSubmodelElements(null); + + return new ApiResponse<>(localVarResponse.statusCode(), localVarResponse.headers().map(), deserializedSubmodel); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder getSubmodelByIdMetadataRequestBuilder(String submodelIdentifier, String level) throws ApiException { + // verify the required parameter 'submodelIdentifier' is set + if (submodelIdentifier == null) { + throw new ApiException(400, "Missing the required parameter 'submodelIdentifier' when calling getSubmodelByIdMetadata"); + } + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/submodels/{submodelIdentifier}/$metadata".replace("{submodelIdentifier}", ApiClient.urlEncode(submodelIdentifier.toString())); + + List localVarQueryParams = new ArrayList<>(); + StringJoiner localVarQueryStringJoiner = new StringJoiner("&"); + String localVarQueryParameterBaseName; + localVarQueryParameterBaseName = "level"; + localVarQueryParams.addAll(ApiClient.parameterToPairs("level", level)); + + if (!localVarQueryParams.isEmpty() || localVarQueryStringJoiner.length() != 0) { + StringJoiner queryJoiner = new StringJoiner("&"); + localVarQueryParams.forEach(p -> queryJoiner.add(p.getName() + '=' + p.getValue())); + if (localVarQueryStringJoiner.length() != 0) { + queryJoiner.add(localVarQueryStringJoiner.toString()); + } + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath + '?' + queryJoiner.toString())); + } else { + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + } + + localVarRequestBuilder.header("Accept", "application/json"); + + addAuthorizationHeaderIfAuthIsEnabled(localVarRequestBuilder); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + /** * Creates a new Submodel * diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index ce9c3aa04..e094bfdb8 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -48,6 +48,7 @@ import org.eclipse.digitaltwin.basyx.core.exceptions.NotInvokableException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.serialization.SubmodelMetadataUtil; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelservice.DummySubmodelFactory; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; @@ -280,7 +281,7 @@ public void getSubmodelByIdMetadata() throws JsonProcessingException { Submodel expectedSubmodel = buildDummySubmodelWithNoSmElement(ID); expectedSubmodel.setSubmodelElements(null); repo.createSubmodel(expectedSubmodel); - + Submodel retrievedSubmodelMetadata = repo.getSubmodelByIdMetadata(ID); retrievedSubmodelMetadata.setSubmodelElements(null); @@ -347,6 +348,44 @@ public void invokeNonOperation() { submodelRepo.invokeOperation(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT, new OperationVariable[0]); } + + @Test + public void getSubmodelByIdValueOnlyExistingSubmodel() { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + + String submodelId = DummySubmodelFactory.SUBMODEL_OPERATIONAL_DATA_ID; + Submodel expectedSubmodel = DummySubmodelFactory.createOperationalDataSubmodel(); + SubmodelValueOnly expectedValueOnly = new SubmodelValueOnly(expectedSubmodel.getSubmodelElements()); + + SubmodelValueOnly valueOnly = repo.getSubmodelByIdValueOnly(submodelId); + + assertEquals(expectedValueOnly.getIdShort(), valueOnly.getIdShort()); + assertEquals(expectedValueOnly.getValuesOnlyMap(), valueOnly.getValuesOnlyMap()); + } + + @Test(expected = ElementDoesNotExistException.class) + public void getSubmodelByIdValueOnlyNonExistingSubmodel() { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + repo.getSubmodelByIdValueOnly("nonExistingSubmodelId"); + } + + @Test + public void getSubmodelByIdMetadataExistingSubmodel() { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + + Submodel expectedMetadata = SubmodelMetadataUtil.extractMetadata(DummySubmodelFactory.createOperationalDataSubmodel()); + String submodelId = expectedMetadata.getId(); + + Submodel metadata = repo.getSubmodelByIdMetadata(submodelId); + + assertEquals(expectedMetadata, metadata); + } + + @Test(expected = ElementDoesNotExistException.class) + public void getSubmodelByIdMetadataNonExistingSubmodel() { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + repo.getSubmodelByIdMetadata("nonExistingSubmodelId"); + } private Submodel buildDummySubmodel(String id) { return new DefaultSubmodel.Builder().id(id).submodelElements(new DefaultProperty.Builder().idShort("prop").value("testValue").valueType(DataTypeDefXsd.STRING).build()).build(); diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java index a00b76091..9d33255e9 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java @@ -407,6 +407,21 @@ public void getFileFromNotExistElement() throws FileNotFoundException, Unsupport assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); } + @Test + public void getSubmodelByIdValueOnly() throws IOException, ParseException { + String submodelIdentifier = Base64UrlEncodedIdentifier.encodeIdentifier(DummySubmodelFactory.createTechnicalDataSubmodel().getId()); + String url = getURL() + "/" + submodelIdentifier + "/$value"; + + String expectedSubmodelJSON = getJSONValueAsString("SingleSubmodelValueOnly.json"); + + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(url); + assertEquals(HttpStatus.OK.value(), response.getCode()); + + String actualSubmodelJSON = BaSyxHttpTestUtils.getResponseAsString(response); + + BaSyxHttpTestUtils.assertSameJSONContent(expectedSubmodelJSON, actualSubmodelJSON); + } + private String extractFileNameFromContentDisposition(String contentDisposition) { for (String part : contentDisposition.split(";")) { part = part.trim(); diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodelValueOnly.json b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodelValueOnly.json new file mode 100644 index 000000000..89ca82af2 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodelValueOnly.json @@ -0,0 +1,117 @@ +{ + "SubmodelElementList": [ + { + "min": 200, + "max": 300 + }, + "5000" + ], + "EntityData": { + "statements": [ + { + "MaxRotationSpeed": "5000" + }, + { + "RotationSpeedRange": { + "min": 200, + "max": 300 + } + } + ], + "entityType": "CoManagedEntity", + "globalAssetId": "globalAssetID", + "specificAssetIds": [ + { + "specificAssetIdName": "specificValue" + } + ] + }, + "RelationshipElement": { + "first": { + "type": "ModelReference", + "keys": [ + { + "type": "DataElement", + "value": "DataElement" + } + ] + }, + "second": { + "type": "ExternalReference", + "keys": [ + { + "type": "BasicEventElement", + "value": "BasicEventElement" + } + ] + } + }, + "ReferenceElement": { + "type": "ModelReference", + "keys": [ + { + "type": "DataElement", + "value": "DataElement" + } + ] + }, + "BlobData": { + "contentType": "application/xml", + "value": "Test content of XML file" + }, + "SubmodelElementCollection": { + "MaxRotationSpeed": "5000", + "FileData": { + "contentType": "application/json", + "value": "testFile.json" + } + }, + "MultiLanguage": [ + { + "en": "Hello" + }, + { + "de": "Hallo" + } + ], + "AnnotatedRelationshipElement": { + "first": { + "type": "ModelReference", + "keys": [ + { + "type": "DataElement", + "value": "DataElement" + } + ] + }, + "second": { + "type": "ExternalReference", + "keys": [ + { + "type": "BasicEventElement", + "value": "BasicEventElement" + } + ] + }, + "annotation": [ + { + "MaxRotationSpeed": "5000" + }, + { + "RotationSpeedRange": { + "min": 200, + "max": 300 + } + } + ] + }, + "MaxRotationSpeed": "5000", + "RotationSpeedRange": { + "min": 200, + "max": 300 + }, + "FileData": { + "contentType": "application/json", + "value": "testFile.json" + } +} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/value/PropertyValue.java b/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/value/PropertyValue.java index 8f707422c..46df7b6b6 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/value/PropertyValue.java +++ b/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/value/PropertyValue.java @@ -47,4 +47,12 @@ public PropertyValue(String value) { public String getValue() { return value; } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + PropertyValue that = (PropertyValue) obj; + return value.equals(that.value); + } }