2626
2727import java .io .File ;
2828import java .io .InputStream ;
29+ import java .util .Arrays ;
2930import java .util .List ;
31+ import java .util .Objects ;
3032
3133import org .eclipse .digitaltwin .aas4j .v3 .model .AssetAdministrationShell ;
3234import org .eclipse .digitaltwin .aas4j .v3 .model .AssetInformation ;
5456 *
5557 */
5658public class RegistryIntegrationAasRepository implements AasRepository {
59+ private static final int SHELL_LOCK_COUNT = 64 ;
5760 private static Logger logger = LoggerFactory .getLogger (RegistryIntegrationAasRepository .class );
5861
5962 private AasRepository decorated ;
6063
6164 private AasRepositoryRegistryLink aasRepositoryRegistryLink ;
6265 private AttributeMapper attributeMapper ;
66+ private final Object [] shellMutationLocks = new Object [SHELL_LOCK_COUNT ];
6367
6468 public RegistryIntegrationAasRepository (AasRepository decorated , AasRepositoryRegistryLink aasRepositoryRegistryLink , AttributeMapper attributeMapper ) {
6569 this .decorated = decorated ;
6670 this .aasRepositoryRegistryLink = aasRepositoryRegistryLink ;
6771 this .attributeMapper = attributeMapper ;
72+ Arrays .setAll (shellMutationLocks , index -> new Object ());
6873 }
6974
7075 @ Override
@@ -79,40 +84,45 @@ public AssetAdministrationShell getAas(String shellId) throws ElementDoesNotExis
7984
8085 @ Override
8186 public void createAas (AssetAdministrationShell shell ) throws CollidingIdentifierException {
82- AssetAdministrationShellDescriptor descriptor = createDescriptor (shell );
87+ synchronized (getShellMutationLock (shell .getId ())) {
88+ AssetAdministrationShellDescriptor descriptor = createDescriptor (shell );
8389
84- decorated .createAas (shell );
90+ decorated .createAas (shell );
8591
86- boolean registrationSuccessful = false ;
92+ boolean registrationSuccessful = false ;
8793
88- try {
89- registerAas (descriptor );
90- registrationSuccessful = true ;
91- } finally {
92- if (!registrationSuccessful )
93- decorated .deleteAas (shell .getId ());
94+ try {
95+ registerAas (descriptor );
96+ registrationSuccessful = true ;
97+ } finally {
98+ if (!registrationSuccessful )
99+ decorated .deleteAas (shell .getId ());
100+ }
94101 }
95102 }
96103
97104 @ Override
98105 public void updateAas (String shellId , AssetAdministrationShell shell ) {
99- AssetAdministrationShell previousShell = decorated .getAas (shellId );
106+ synchronized (getShellMutationLock (shellId )) {
107+ AssetAdministrationShell previousShell = decorated .getAas (shellId );
100108
101- decorated .updateAas (shellId , shell );
109+ decorated .updateAas (shellId , shell );
102110
103- try {
104- updateDescriptor (shellId , shell );
105- } catch (RuntimeException e ) {
106- rollbackAasUpdate (shellId , previousShell , e );
107- throw e ;
111+ try {
112+ updateDescriptor (shellId , shell );
113+ } catch (RuntimeException e ) {
114+ rollbackAasUpdate (shellId , previousShell , e );
115+ throw e ;
116+ }
108117 }
109118 }
110119
111120 @ Override
112121 public void deleteAas (String shellId ) {
113- deleteFromRegistry (shellId );
114-
115- decorated .deleteAas (shellId );
122+ synchronized (getShellMutationLock (shellId )) {
123+ deleteFromRegistry (shellId );
124+ decorated .deleteAas (shellId );
125+ }
116126 }
117127
118128 @ Override
@@ -127,25 +137,31 @@ public CursorResult<List<Reference>> getSubmodelReferences(String shellId, Pagin
127137
128138 @ Override
129139 public void addSubmodelReference (String shellId , Reference submodelReference ) {
130- decorated .addSubmodelReference (shellId , submodelReference );
140+ synchronized (getShellMutationLock (shellId )) {
141+ decorated .addSubmodelReference (shellId , submodelReference );
142+ }
131143 }
132144
133145 @ Override
134146 public void removeSubmodelReference (String shellId , String submodelId ) {
135- decorated .removeSubmodelReference (shellId , submodelId );
147+ synchronized (getShellMutationLock (shellId )) {
148+ decorated .removeSubmodelReference (shellId , submodelId );
149+ }
136150 }
137151
138152 @ Override
139153 public void setAssetInformation (String shellId , AssetInformation shellInfo ) throws ElementDoesNotExistException {
140- AssetInformation previousAssetInformation = decorated .getAssetInformation (shellId );
154+ synchronized (getShellMutationLock (shellId )) {
155+ AssetInformation previousAssetInformation = decorated .getAssetInformation (shellId );
141156
142- decorated .setAssetInformation (shellId , shellInfo );
157+ decorated .setAssetInformation (shellId , shellInfo );
143158
144- try {
145- updateDescriptor (shellId , decorated .getAas (shellId ));
146- } catch (RuntimeException e ) {
147- rollbackAssetInformationUpdate (shellId , previousAssetInformation , e );
148- throw e ;
159+ try {
160+ updateDescriptor (shellId , decorated .getAas (shellId ));
161+ } catch (RuntimeException e ) {
162+ rollbackAssetInformationUpdate (shellId , previousAssetInformation , e );
163+ throw e ;
164+ }
149165 }
150166 }
151167
@@ -168,25 +184,75 @@ private void registerAas(AssetAdministrationShellDescriptor descriptor) {
168184
169185 private void updateDescriptor (String shellId , AssetAdministrationShell shell ) {
170186 RegistryAndDiscoveryInterfaceApi registryApi = aasRepositoryRegistryLink .getRegistryApi ();
187+ AssetAdministrationShellDescriptor existingDescriptor ;
171188
172189 try {
173- AssetAdministrationShellDescriptor existingDescriptor = registryApi .getAssetAdministrationShellDescriptorById (shellId );
174- AssetAdministrationShellDescriptor updatedDescriptor = createDescriptor (shell );
175- updatedDescriptor .setSubmodelDescriptors (existingDescriptor .getSubmodelDescriptors ());
190+ existingDescriptor = registryApi .getAssetAdministrationShellDescriptorById (shellId );
191+ } catch (ApiException e ) {
192+ throw createRegistryLinkException (shellId , "update" , e );
193+ }
176194
195+ AssetAdministrationShellDescriptor updatedDescriptor = createDescriptor (shell );
196+ preserveRegistryManagedAttributes (existingDescriptor , updatedDescriptor );
197+
198+ try {
177199 registryApi .putAssetAdministrationShellDescriptorById (shellId , updatedDescriptor );
178200
179201 logger .info ("Shell descriptor '{}' has been automatically updated in the Registry" , shellId );
180202 } catch (ApiException e ) {
203+ if (descriptorMatchesAasDerivedState (shellId , updatedDescriptor , registryApi , e )) {
204+ logger .warn ("Registry update for shell descriptor '{}' returned an error, but a read-back confirmed the requested AAS-derived state." , shellId );
205+ return ;
206+ }
207+
181208 throw createRegistryLinkException (shellId , "update" , e );
182209 }
183210 }
184211
212+ private void preserveRegistryManagedAttributes (AssetAdministrationShellDescriptor existingDescriptor , AssetAdministrationShellDescriptor updatedDescriptor ) {
213+ if (existingDescriptor .getEndpoints () != null )
214+ updatedDescriptor .setEndpoints (existingDescriptor .getEndpoints ());
215+
216+ updatedDescriptor .setSubmodelDescriptors (existingDescriptor .getSubmodelDescriptors ());
217+ }
218+
219+ private boolean descriptorMatchesAasDerivedState (String shellId , AssetAdministrationShellDescriptor expectedDescriptor , RegistryAndDiscoveryInterfaceApi registryApi , ApiException updateException ) {
220+ try {
221+ AssetAdministrationShellDescriptor actualDescriptor = registryApi .getAssetAdministrationShellDescriptorById (shellId );
222+ return aasDerivedAttributesEqual (expectedDescriptor , actualDescriptor );
223+ } catch (ApiException verificationException ) {
224+ updateException .addSuppressed (verificationException );
225+ return false ;
226+ }
227+ }
228+
229+ private boolean aasDerivedAttributesEqual (AssetAdministrationShellDescriptor expected , AssetAdministrationShellDescriptor actual ) {
230+ return actual != null
231+ && Objects .equals (expected .getAdministration (), actual .getAdministration ())
232+ && Objects .equals (expected .getAssetKind (), actual .getAssetKind ())
233+ && Objects .equals (expected .getAssetType (), actual .getAssetType ())
234+ && Objects .equals (expected .getDescription (), actual .getDescription ())
235+ && Objects .equals (expected .getDisplayName (), actual .getDisplayName ())
236+ && Objects .equals (expected .getExtensions (), actual .getExtensions ())
237+ && Objects .equals (expected .getGlobalAssetId (), actual .getGlobalAssetId ())
238+ && Objects .equals (expected .getId (), actual .getId ())
239+ && Objects .equals (expected .getIdShort (), actual .getIdShort ())
240+ && Objects .equals (expected .getSpecificAssetIds (), actual .getSpecificAssetIds ());
241+ }
242+
185243 private AssetAdministrationShellDescriptor createDescriptor (AssetAdministrationShell shell ) {
186244 return new AasDescriptorFactory (aasRepositoryRegistryLink .getAasRepositoryBaseURLs (), attributeMapper ).create (shell );
187245 }
188246
189247 private RepositoryRegistryLinkException createRegistryLinkException (String shellId , String operation , ApiException cause ) {
248+ return new RepositoryRegistryLinkException (shellId , createRegistryFailureDetails (operation , cause ), cause );
249+ }
250+
251+ private RepositoryRegistryUnlinkException createRegistryUnlinkException (String shellId , ApiException cause ) {
252+ return new RepositoryRegistryUnlinkException (shellId , createRegistryFailureDetails ("deletion" , cause ), cause );
253+ }
254+
255+ private String createRegistryFailureDetails (String operation , ApiException cause ) {
190256 StringBuilder details = new StringBuilder ("Registry descriptor " ).append (operation ).append (" failed" );
191257
192258 if (cause .getCode () > 0 )
@@ -199,7 +265,7 @@ private RepositoryRegistryLinkException createRegistryLinkException(String shell
199265 if (responseDetails != null && !responseDetails .isBlank ())
200266 details .append (": " ).append (responseDetails );
201267
202- return new RepositoryRegistryLinkException ( shellId , details .toString (), cause );
268+ return details .toString ();
203269 }
204270
205271 private void rollbackAasUpdate (String shellId , AssetAdministrationShell previousShell , RuntimeException updateException ) {
@@ -222,30 +288,23 @@ private void rollbackAssetInformationUpdate(String shellId, AssetInformation pre
222288
223289 private void deleteFromRegistry (String shellId ) {
224290 RegistryAndDiscoveryInterfaceApi registryApi = aasRepositoryRegistryLink .getRegistryApi ();
225-
226- if (!shellExistsOnRegistry (shellId , registryApi )) {
227- logger .error ("Unable to un-link the AAS descriptor '{}' from the Registry because it does not exist on the Registry." , shellId );
228-
229- return ;
230- }
231291
232292 try {
233293 registryApi .deleteAssetAdministrationShellDescriptorById (shellId );
234294
235295 logger .info ("Shell '{}' has been automatically un-linked from the Registry." , shellId );
236296 } catch (ApiException e ) {
237- throw new RepositoryRegistryUnlinkException (shellId , e );
297+ if (e .getCode () == 404 ) {
298+ logger .info ("Shell descriptor '{}' was already absent from the Registry." , shellId );
299+ return ;
300+ }
301+
302+ throw createRegistryUnlinkException (shellId , e );
238303 }
239304 }
240-
241- private boolean shellExistsOnRegistry (String shellId , RegistryAndDiscoveryInterfaceApi registryApi ) {
242- try {
243- registryApi .getAssetAdministrationShellDescriptorById (shellId );
244-
245- return true ;
246- } catch (ApiException e ) {
247- return false ;
248- }
305+
306+ private Object getShellMutationLock (String shellId ) {
307+ return shellMutationLocks [Math .floorMod (Objects .hashCode (shellId ), shellMutationLocks .length )];
249308 }
250309
251310 @ Override
0 commit comments