From cf226cb7b6c91b4025dbef2f8264bb91e4d8b344 Mon Sep 17 00:00:00 2001 From: Sankalp Thakur Date: Sat, 5 Sep 2026 19:32:37 +0400 Subject: [PATCH] Fix SpecificAssetId JSON for SelfManagedEntity AAS4J's Entity XML mixin does not declare the specificAssetId item wrapper used by AssetInformation. XML-loaded Entity specificAssetIds were empty objects, so GET responses serialized as [{}]. Apply that wrapper mixin when loading XML/AASX, and expose SpecificAssetId name/value/semanticId/externalSubjectId on the HTTP ObjectMapper. Fixes #933 Signed-off-by: Sankalp Thakur --- .../BasyxXmlDeserializer.java | 54 +++++++++ .../CompleteEnvironment.java | 4 +- .../EntitySpecificAssetIdsXmlMixin.java | 65 +++++++++++ .../TestEntitySpecificAssetIdsXmlLoad.java | 88 +++++++++++++++ ...self_managed_entity_specific_asset_ids.xml | 31 ++++++ .../http/Aas4JHTTPSerializationExtension.java | 4 + .../basyx/http/SpecificAssetIdMixin.java | 54 +++++++++ ...ntitySpecificAssetIdJsonSerialization.java | 103 ++++++++++++++++++ 8 files changed, 401 insertions(+), 2 deletions(-) create mode 100644 basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/BasyxXmlDeserializer.java create mode 100644 basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/EntitySpecificAssetIdsXmlMixin.java create mode 100644 basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/TestEntitySpecificAssetIdsXmlLoad.java create mode 100644 basyx.aasenvironment/basyx.aasenvironment-core/src/test/resources/org/eclipse/digitaltwin/basyx/aasenvironment/self_managed_entity_specific_asset_ids.xml create mode 100644 basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/SpecificAssetIdMixin.java create mode 100644 basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/TestEntitySpecificAssetIdJsonSerialization.java diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/BasyxXmlDeserializer.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/BasyxXmlDeserializer.java new file mode 100644 index 000000000..e1d850e20 --- /dev/null +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/BasyxXmlDeserializer.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (C) 2026 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.xml.XmlDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.model.Entity; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEntity; + +import com.fasterxml.jackson.dataformat.xml.XmlFactory; + +/** + * XmlDeserializer that maps Entity {@code specificAssetIds} using the same + * item/wrapper names as AssetInformation. + */ +public class BasyxXmlDeserializer extends XmlDeserializer { + + public BasyxXmlDeserializer() { + super(); + } + + public BasyxXmlDeserializer(XmlFactory xmlFactory) { + super(xmlFactory); + } + + @Override + protected void buildMapper() { + super.buildMapper(); + mapper.addMixIn(Entity.class, EntitySpecificAssetIdsXmlMixin.class); + mapper.addMixIn(DefaultEntity.class, EntitySpecificAssetIdsXmlMixin.class); + } +} diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/CompleteEnvironment.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/CompleteEnvironment.java index 2e75dad9d..ef7fa7a8d 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/CompleteEnvironment.java +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/CompleteEnvironment.java @@ -106,12 +106,12 @@ public static CompleteEnvironment fromInputStream(InputStream inputStream, Envir environment = deserializer.read(inputStream, Environment.class); } if(envType == EnvironmentType.XML) { - XmlDeserializer deserializer = new XmlDeserializer(); + XmlDeserializer deserializer = new BasyxXmlDeserializer(); environment = deserializer.read(inputStream); } if(envType == EnvironmentType.AASX) { try { - AASXDeserializer deserializer = new AASXDeserializer(inputStream); + AASXDeserializer deserializer = new AASXDeserializer(new BasyxXmlDeserializer(), inputStream); relatedFiles = deserializer.getRelatedFiles(); environment = deserializer.read(); } catch (Exception e) { diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/EntitySpecificAssetIdsXmlMixin.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/EntitySpecificAssetIdsXmlMixin.java new file mode 100644 index 000000000..65e14c14a --- /dev/null +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/EntitySpecificAssetIdsXmlMixin.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (C) 2026 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader; + +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.xml.internal.AasXmlNamespaceContext; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.xml.internal.deserialization.SubmodelElementsDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.xml.internal.serialization.SubmodelElementsSerializer; +import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +/** + * AAS4J's Entity XML mixin omits the {@code specificAssetId} item wrapper that + * {@code AssetInformation} declares. Without it, Entity {@code specificAssetIds} + * deserialize as empty objects and JSON GET responses emit {@code [{}]}. + */ +public abstract class EntitySpecificAssetIdsXmlMixin { + + @JacksonXmlProperty(namespace = AasXmlNamespaceContext.AAS_URI, localName = "statements") + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(using = SubmodelElementsSerializer.class) + @JsonDeserialize(using = SubmodelElementsDeserializer.class) + public abstract List getStatements(); + + @JacksonXmlProperty(namespace = AasXmlNamespaceContext.AAS_URI, localName = "globalAssetId") + public abstract void setGlobalAssetID(String globalAssetID); + + @JacksonXmlProperty(namespace = AasXmlNamespaceContext.AAS_URI, localName = "specificAssetId") + @JacksonXmlElementWrapper(namespace = AasXmlNamespaceContext.AAS_URI, localName = "specificAssetIds") + public abstract List getSpecificAssetIds(); + + @JacksonXmlProperty(namespace = AasXmlNamespaceContext.AAS_URI, localName = "specificAssetId") + @JacksonXmlElementWrapper(namespace = AasXmlNamespaceContext.AAS_URI, localName = "specificAssetIds") + public abstract void setSpecificAssetIds(List specificAssetIds); +} diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/TestEntitySpecificAssetIdsXmlLoad.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/TestEntitySpecificAssetIdsXmlLoad.java new file mode 100644 index 000000000..9ab4270ed --- /dev/null +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/TestEntitySpecificAssetIdsXmlLoad.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (C) 2026 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasenvironment; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import java.io.InputStream; +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonSerializer; +import org.eclipse.digitaltwin.aas4j.v3.model.Entity; +import org.eclipse.digitaltwin.aas4j.v3.model.EntityType; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment; +import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment.EnvironmentType; +import org.junit.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Regression for #933: XML-loaded SelfManagedEntity must keep SpecificAssetId + * fields and must not JSON-serialize them as {@code {}}. + */ +public class TestEntitySpecificAssetIdsXmlLoad { + + private static final String XML_RESOURCE = "/org/eclipse/digitaltwin/basyx/aasenvironment/self_managed_entity_specific_asset_ids.xml"; + private static final String ASSET_ID_NAME = "https://admin-shell.io/idta/test"; + private static final String ASSET_ID_VALUE = "Value1"; + + @Test + public void xmlLoadedSelfManagedEntitySpecificAssetIdIsNotEmptyObject() throws Exception { + Environment environment; + try (InputStream xml = getClass().getResourceAsStream(XML_RESOURCE)) { + environment = CompleteEnvironment.fromInputStream(xml, EnvironmentType.XML).getEnvironment(); + } + + Entity entity = findEntryNode(environment); + assertEquals(EntityType.SELF_MANAGED_ENTITY, entity.getEntityType()); + + List specificAssetIds = entity.getSpecificAssetIds(); + assertEquals(1, specificAssetIds.size()); + SpecificAssetId specificAssetId = specificAssetIds.get(0); + assertEquals(ASSET_ID_NAME, specificAssetId.getName()); + assertEquals(ASSET_ID_VALUE, specificAssetId.getValue()); + assertNotNull(specificAssetId.getSemanticId()); + + JsonNode json = new ObjectMapper().readTree(new JsonSerializer().write(entity)); + JsonNode serializedIds = json.get("specificAssetIds"); + assertEquals(1, serializedIds.size()); + assertFalse("specificAssetIds must not serialize as {}", serializedIds.get(0).isEmpty()); + assertEquals(ASSET_ID_NAME, serializedIds.get(0).get("name").asText()); + assertEquals(ASSET_ID_VALUE, serializedIds.get(0).get("value").asText()); + } + + private static Entity findEntryNode(Environment environment) { + SubmodelElement element = environment.getSubmodels().get(0).getSubmodelElements().get(0); + assertEquals("EntryNode", element.getIdShort()); + return (Entity) element; + } +} diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/test/resources/org/eclipse/digitaltwin/basyx/aasenvironment/self_managed_entity_specific_asset_ids.xml b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/resources/org/eclipse/digitaltwin/basyx/aasenvironment/self_managed_entity_specific_asset_ids.xml new file mode 100644 index 000000000..25135ec90 --- /dev/null +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/resources/org/eclipse/digitaltwin/basyx/aasenvironment/self_managed_entity_specific_asset_ids.xml @@ -0,0 +1,31 @@ + + + + + HierarchicalStructures + https://admin-shell.io/idta/SubmodelTemplate/HierarchicalStructuresBoM/1/1 + + + EntryNode + SelfManagedEntity + https://admin-shell.io/idta/HierarchicalStructures/EntryNode/1/0 + + + + ExternalReference + + + GlobalReference + https://admin-shell.io/idta/test + + + + https://admin-shell.io/idta/test + Value1 + + + + + + + diff --git a/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/Aas4JHTTPSerializationExtension.java b/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/Aas4JHTTPSerializationExtension.java index 47a9d7e05..4a1fbf9e4 100644 --- a/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/Aas4JHTTPSerializationExtension.java +++ b/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/Aas4JHTTPSerializationExtension.java @@ -29,6 +29,8 @@ import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.serialization.EnumSerializer; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.util.ReflectionHelper; import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.internal.ReflectionAnnotationIntrospector; +import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSpecificAssetId; import org.eclipse.digitaltwin.basyx.core.StandardizedLiteralEnum; import org.eclipse.digitaltwin.basyx.http.description.Profile; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; @@ -61,6 +63,8 @@ public void extend(Jackson2ObjectMapperBuilder builder) { .annotationIntrospector(new ReflectionAnnotationIntrospector()) .modulesToInstall(buildEnumModule(), buildImplementationModule()); ReflectionHelper.JSON_MIXINS.entrySet().forEach(x -> builder.mixIn(x.getKey(), x.getValue())); + builder.mixIn(SpecificAssetId.class, SpecificAssetIdMixin.class); + builder.mixIn(DefaultSpecificAssetId.class, SpecificAssetIdMixin.class); } @SuppressWarnings("unchecked") diff --git a/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/SpecificAssetIdMixin.java b/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/SpecificAssetIdMixin.java new file mode 100644 index 000000000..bfcedf4a4 --- /dev/null +++ b/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/SpecificAssetIdMixin.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (C) 2026 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.http; + +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Ensures SpecificAssetId name/value/semanticId/externalSubjectId are visible + * to Jackson. AAS4J does not ship a JSON mixin for this type. + */ +public abstract class SpecificAssetIdMixin { + + @JsonProperty("name") + public abstract String getName(); + + @JsonProperty("value") + public abstract String getValue(); + + @JsonProperty("semanticId") + public abstract Reference getSemanticId(); + + @JsonProperty("supplementalSemanticIds") + public abstract List getSupplementalSemanticIds(); + + @JsonProperty("externalSubjectId") + public abstract Reference getExternalSubjectId(); +} diff --git a/basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/TestEntitySpecificAssetIdJsonSerialization.java b/basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/TestEntitySpecificAssetIdJsonSerialization.java new file mode 100644 index 000000000..83a2fa703 --- /dev/null +++ b/basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/TestEntitySpecificAssetIdJsonSerialization.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (C) 2026 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.http.serialization; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.Entity; +import org.eclipse.digitaltwin.aas4j.v3.model.EntityType; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEntity; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultKey; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSpecificAssetId; +import org.eclipse.digitaltwin.basyx.http.Aas4JHTTPSerializationExtension; +import org.eclipse.digitaltwin.basyx.http.BaSyxHTTPConfiguration; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Regression for #933: SelfManagedEntity specificAssetIds must serialize + * name/value/semanticId rather than an empty object. + */ +public class TestEntitySpecificAssetIdJsonSerialization { + + private static final String ASSET_ID_NAME = "https://admin-shell.io/idta/test"; + private static final String ASSET_ID_VALUE = "Value1"; + private static final String SEMANTIC_ID_VALUE = "https://admin-shell.io/idta/test"; + private static final String SUBJECT_ID_VALUE = "https://example.com/subject"; + + private ObjectMapper mapper; + + @Before + public void setUp() { + Jackson2ObjectMapperBuilder builder = new BaSyxHTTPConfiguration() + .jackson2ObjectMapperBuilder(List.of(new Aas4JHTTPSerializationExtension())); + mapper = builder.build(); + } + + @Test + public void selfManagedEntitySpecificAssetIdIsNotEmptyObject() throws Exception { + Entity entity = new DefaultEntity.Builder().idShort("EntryNode") + .entityType(EntityType.SELF_MANAGED_ENTITY) + .globalAssetId("https://admin-shell.io/idta/HierarchicalStructures/EntryNode/1/0") + .specificAssetIds(List.of(new DefaultSpecificAssetId.Builder().name(ASSET_ID_NAME) + .value(ASSET_ID_VALUE) + .semanticId(new DefaultReference.Builder().type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder().type(KeyTypes.GLOBAL_REFERENCE) + .value(SEMANTIC_ID_VALUE) + .build()) + .build()) + .externalSubjectId(new DefaultReference.Builder().type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder().type(KeyTypes.GLOBAL_REFERENCE) + .value(SUBJECT_ID_VALUE) + .build()) + .build()) + .build())) + .build(); + + JsonNode json = mapper.readTree(mapper.writeValueAsString(entity)); + JsonNode specificAssetIds = json.get("specificAssetIds"); + + assertTrue(specificAssetIds.isArray()); + assertEquals(1, specificAssetIds.size()); + JsonNode assetId = specificAssetIds.get(0); + assertFalse("specificAssetIds must not serialize as {}", assetId.isEmpty()); + assertEquals(ASSET_ID_NAME, assetId.get("name").asText()); + assertEquals(ASSET_ID_VALUE, assetId.get("value").asText()); + assertEquals(SEMANTIC_ID_VALUE, assetId.get("semanticId").get("keys").get(0).get("value").asText()); + assertEquals(SUBJECT_ID_VALUE, assetId.get("externalSubjectId").get("keys").get(0).get("value").asText()); + } +}