Skip to content

Commit 5149bfe

Browse files
FriedJannikCopilot
andauthored
Adds Pagination on Backend (#833)
* Adds Pagination on Backend * Adds Pagination on Backend (AAS Repo) * Adds Pagination on Backend (AAS Repo) * Update basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/MongoDBAasOperations.java Co-authored-by: Copilot <[email protected]> * Update basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelBackend.java Co-authored-by: Copilot <[email protected]> * Update basyx.aasservice/basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/InMemoryAasBackend.java Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent 4bcd810 commit 5149bfe

File tree

8 files changed

+183
-51
lines changed
  • basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend
  • basyx.aasservice
    • basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend
    • basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend
    • basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend
  • basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend
  • basyx.submodelservice

8 files changed

+183
-51
lines changed

basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepository.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,7 @@ public CrudAasRepository(AasBackend aasBackend, AasServiceFactory aasServiceFact
7070

7171
@Override
7272
public CursorResult<List<AssetAdministrationShell>> getAllAas(List<SpecificAssetId> assetIds, String idShort, PaginationInfo pInfo) {
73-
74-
Iterable<AssetAdministrationShell> iterable = aasBackend.getAllAas(assetIds, idShort);
75-
List<AssetAdministrationShell> allAas = StreamSupport.stream(iterable.spliterator(), false).toList();
76-
77-
TreeMap<String, AssetAdministrationShell> aasMap = allAas.stream().collect(Collectors.toMap(AssetAdministrationShell::getId, aas -> aas, (a, b) -> a, TreeMap::new));
78-
79-
PaginationSupport<AssetAdministrationShell> paginationSupport = new PaginationSupport<>(aasMap, AssetAdministrationShell::getId);
80-
81-
return paginationSupport.getPaged(pInfo);
73+
return aasBackend.getShells(assetIds, idShort, pInfo);
8274
}
8375

8476
@Override

basyx.aasservice/basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/InMemoryAasBackend.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.util.TreeMap;
4141
import java.util.function.Function;
4242
import java.util.stream.Collectors;
43+
import java.util.stream.StreamSupport;
4344

4445
/**
4546
* Implements the AasService as in-memory variant
@@ -55,6 +56,18 @@ public InMemoryAasBackend() {
5556
super(AssetAdministrationShell::getId);
5657
}
5758

59+
@Override
60+
public CursorResult<List<AssetAdministrationShell>> getShells(List<SpecificAssetId> assetIds, String idShort, PaginationInfo pInfo) {
61+
Iterable<AssetAdministrationShell> iterable = getAllAas(assetIds, idShort);
62+
List<AssetAdministrationShell> allAas = StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
63+
64+
TreeMap<String, AssetAdministrationShell> aasMap = allAas.stream().collect(Collectors.toMap(AssetAdministrationShell::getId, aas -> aas, (a, b) -> a, TreeMap::new));
65+
66+
PaginationSupport<AssetAdministrationShell> paginationSupport = new PaginationSupport<>(aasMap, AssetAdministrationShell::getId);
67+
68+
return paginationSupport.getPaged(pInfo);
69+
}
70+
5871
@Override
5972
public CursorResult<List<Reference>> getSubmodelReferences(String aasId, PaginationInfo pInfo) {
6073
List<Reference> submodelReferences = getAas(aasId).getSubmodels();

basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/MongoDBAasOperations.java

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException;
3535
import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult;
3636
import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo;
37+
import org.springframework.data.domain.Sort;
3738
import org.springframework.data.mongodb.core.MongoOperations;
3839
import org.springframework.data.mongodb.core.aggregation.Aggregation;
3940
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
@@ -67,6 +68,42 @@ public MongoDBAasOperations(MongoOperations mongoOperations) {
6768
collectionName = mongoOperations.getCollectionName(AssetAdministrationShell.class);
6869
}
6970

71+
@Override
72+
public CursorResult<List<AssetAdministrationShell>> getShells(List<SpecificAssetId> assetIds, String idShort, PaginationInfo pInfo) {
73+
List<AggregationOperation> ops = new ArrayList<>();
74+
75+
List<Criteria> criteriaList = buildAasFilterCriteria(assetIds, idShort);
76+
if (!criteriaList.isEmpty()) {
77+
ops.add(Aggregation.match(new Criteria().andOperator(criteriaList.toArray(new Criteria[0]))));
78+
}
79+
80+
// Apply cursor (_id > cursor)
81+
if (hasCursor(pInfo)) {
82+
ops.add(Aggregation.match(Criteria.where("_id").gt(pInfo.getCursor())));
83+
}
84+
85+
// Sort for stable pagination
86+
ops.add(Aggregation.sort(Sort.by(Sort.Direction.ASC, "_id")));
87+
88+
// Apply limit
89+
if (hasLimit(pInfo)) {
90+
ops.add(Aggregation.limit(pInfo.getLimit()));
91+
}
92+
93+
// Run aggregation
94+
Aggregation aggregation = Aggregation.newAggregation(ops);
95+
AggregationResults<AssetAdministrationShell> results =
96+
mongoOperations.aggregate(aggregation, collectionName, AssetAdministrationShell.class);
97+
98+
List<AssetAdministrationShell> shells = results.getMappedResults();
99+
100+
String nextCursor = shells.isEmpty()
101+
? null
102+
: shells.get(shells.size() - 1).getId();
103+
104+
return new CursorResult<>(nextCursor, shells);
105+
}
106+
70107

71108
@Override
72109
public CursorResult<List<Reference>> getSubmodelReferences(@NonNull String aasId, @NonNull PaginationInfo pInfo) throws ElementDoesNotExistException {
@@ -174,29 +211,10 @@ public AssetInformation getAssetInformation(@NonNull String aasId) {
174211
@Override
175212
public Iterable<AssetAdministrationShell> getAllAas(List<SpecificAssetId> assetIds, String idShort) {
176213
Query query = new Query();
177-
Criteria criteria = new Criteria();
178-
179-
String globalAssetId = null;
180-
try {
181-
globalAssetId = assetIds.stream().filter(assetId -> assetId.getName().equals("globalAssetId")).findFirst().get().getValue();
182-
assetIds = assetIds.stream().filter(assetId -> !assetId.getName().equals("globalAssetId")).collect(Collectors.toList());
183-
} catch (Exception ignored) {}
214+
List<Criteria> criteriaList = buildAasFilterCriteria(assetIds, idShort);
184215

185-
if (assetIds != null && !assetIds.isEmpty()) {
186-
List<Criteria> assetIdCriteria = new ArrayList<>();
187-
for (SpecificAssetId assetId : assetIds) {
188-
assetIdCriteria.add(Criteria.where("assetInformation.specificAssetIds.name").is(assetId.getName()));
189-
assetIdCriteria.add(Criteria.where("assetInformation.specificAssetIds.value").is(assetId.getValue()));
190-
}
191-
criteria.andOperator(assetIdCriteria);
192-
}
193-
if (idShort != null && !idShort.isEmpty()) {
194-
criteria.and("idShort").is(idShort);
195-
}
196-
query.addCriteria(criteria);
197-
198-
if (globalAssetId != null && !globalAssetId.isEmpty()) {
199-
query.addCriteria(Criteria.where("assetInformation.globalAssetId").is(globalAssetId));
216+
if (!criteriaList.isEmpty()) {
217+
query.addCriteria(new Criteria().andOperator(criteriaList.toArray(new Criteria[0])));
200218
}
201219

202220
return mongoOperations.find(query, AssetAdministrationShell.class, collectionName);
@@ -217,4 +235,51 @@ private static String extractSubmodelId(Reference reference) {
217235

218236
return "";
219237
}
238+
239+
private List<Criteria> buildAasFilterCriteria(List<SpecificAssetId> assetIds, String idShort) {
240+
List<Criteria> criteriaList = new ArrayList<>();
241+
242+
// Extract globalAssetId from assetIds
243+
String globalAssetId = null;
244+
try {
245+
globalAssetId = assetIds.stream()
246+
.filter(assetId -> "globalAssetId".equals(assetId.getName()))
247+
.findFirst()
248+
.map(SpecificAssetId::getValue)
249+
.orElse(null);
250+
251+
assetIds = assetIds.stream()
252+
.filter(assetId -> !"globalAssetId".equals(assetId.getName()))
253+
.collect(Collectors.toList());
254+
} catch (Exception ignored) {}
255+
256+
// Match specific assetIds (name + value pair inside array)
257+
if (assetIds != null && !assetIds.isEmpty()) {
258+
for (SpecificAssetId assetId : assetIds) {
259+
criteriaList.add(Criteria.where("assetInformation.specificAssetIds.name").is(assetId.getName()));
260+
criteriaList.add(Criteria.where("assetInformation.specificAssetIds.value").is(assetId.getValue()));
261+
}
262+
}
263+
264+
// Match idShort if present
265+
if (idShort != null && !idShort.isEmpty()) {
266+
criteriaList.add(Criteria.where("idShort").is(idShort));
267+
}
268+
269+
// Match globalAssetId if present
270+
if (globalAssetId != null && !globalAssetId.isEmpty()) {
271+
criteriaList.add(Criteria.where("assetInformation.globalAssetId").is(globalAssetId));
272+
}
273+
274+
return criteriaList;
275+
}
276+
277+
private static boolean hasLimit(PaginationInfo pInfo) {
278+
return pInfo.getLimit() != null && pInfo.getLimit() > 0;
279+
}
280+
281+
private static boolean hasCursor(PaginationInfo pInfo) {
282+
return pInfo.getCursor() != null && !pInfo.getCursor().isEmpty();
283+
}
284+
220285
}

basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/AasOperations.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@
4444
*/
4545
public interface AasOperations {
4646

47+
/**
48+
* Retrieves all Asset Administration Shells
49+
*
50+
* @param assetIds List of specific asset IDs to filter AASs
51+
* @param idShort idShort to filter AASs
52+
* @param pInfo the pagination information
53+
* @return a {@code CursorResult} containing a list of Asset Administration Shells
54+
*/
55+
CursorResult<List<AssetAdministrationShell>> getShells(List<SpecificAssetId> assetIds, String idShort, PaginationInfo pInfo);
56+
4757
/**
4858
* Retrieves all Submodel References for the given AAS.
4959
*

basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepository.java

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -92,23 +92,7 @@ public CursorResult<List<Submodel>> getAllSubmodels(PaginationInfo pInfo) {
9292

9393
@Override
9494
public CursorResult<List<Submodel>> getAllSubmodels(String semanticId, PaginationInfo pInfo) {
95-
Iterable<Submodel> iterable = submodelBackend.findAll();
96-
List<Submodel> submodels = StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
97-
98-
List<Submodel> filteredSubmodels = submodels.stream()
99-
.filter((submodel) -> {
100-
return submodel.getSemanticId() != null &&
101-
submodel.getSemanticId().getKeys().stream().filter((key) -> {
102-
return key.getValue().equals(semanticId);
103-
}).findAny().isPresent();
104-
})
105-
.collect(Collectors.toList());
106-
107-
TreeMap<String, Submodel> submodelMap = filteredSubmodels.stream().collect(Collectors.toMap(Submodel::getId, submodel -> submodel, (a, b) -> a, TreeMap::new));
108-
109-
PaginationSupport<Submodel> paginationSupport = new PaginationSupport<>(submodelMap, Submodel::getId);
110-
111-
return paginationSupport.getPaged(pInfo);
95+
return submodelBackend.getSubmodels(semanticId, pInfo);
11296
}
11397

11498
@Override

basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelBackend.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.List;
4444
import java.util.TreeMap;
4545
import java.util.stream.Collectors;
46+
import java.util.stream.StreamSupport;
4647

4748
/**
4849
* Implements the SubmodelService as in-memory variant
@@ -57,6 +58,27 @@ public InMemorySubmodelBackend() {
5758
super(Submodel::getId);
5859
}
5960

61+
@Override
62+
public CursorResult<List<Submodel>> getSubmodels(String semanticId, PaginationInfo pInfo) {
63+
Iterable<Submodel> iterable = findAll();
64+
List<Submodel> submodels = StreamSupport.stream(iterable.spliterator(), false).toList();
65+
66+
List<Submodel> filteredSubmodels = submodels.stream()
67+
.filter((submodel) -> {
68+
return submodel.getSemanticId() != null &&
69+
submodel.getSemanticId().getKeys().stream().filter((key) -> {
70+
return key.getValue().equals(semanticId);
71+
}).findAny().isPresent();
72+
})
73+
.collect(Collectors.toList());
74+
75+
TreeMap<String, Submodel> submodelMap = filteredSubmodels.stream().collect(Collectors.toMap(Submodel::getId, submodel -> submodel, (a, b) -> a, TreeMap::new));
76+
77+
PaginationSupport<Submodel> paginationSupport = new PaginationSupport<>(submodelMap, Submodel::getId);
78+
79+
return paginationSupport.getPaged(pInfo);
80+
}
81+
6082
@Override
6183
public CursorResult<List<SubmodelElement>> getSubmodelElements(String submodelId, PaginationInfo pInfo) {
6284
List<SubmodelElement> allSubmodels = getSubmodel(submodelId).getSubmodelElements();

basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/MongoDbSubmodelOperations.java

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue;
4646
import org.eclipse.digitaltwin.basyx.submodelservice.value.factory.SubmodelElementValueMapperFactory;
4747
import org.eclipse.digitaltwin.basyx.submodelservice.value.mapper.ValueMapper;
48+
import org.springframework.data.domain.Sort;
4849
import org.springframework.data.mongodb.core.BulkOperations;
4950
import org.springframework.data.mongodb.core.MongoOperations;
5051
import org.springframework.data.mongodb.core.aggregation.Aggregation;
@@ -74,24 +75,53 @@ public MongoDbSubmodelOperations(MongoOperations mongoOperations) {
7475
this.collectionName = mongoOperations.getCollectionName(Submodel.class);
7576
}
7677

78+
@Override
79+
public CursorResult<List<Submodel>> getSubmodels(String semanticId, PaginationInfo pInfo) {
80+
List<AggregationOperation> ops = new ArrayList<>();
81+
82+
ops.add(Aggregation.match(Criteria.where("semanticId.keys.value").is(semanticId)));
83+
84+
if (hasCursor(pInfo)) {
85+
ops.add(Aggregation.match(Criteria.where("_id").gt(pInfo.getCursor())));
86+
}
87+
88+
ops.add(Aggregation.sort(Sort.by(Sort.Direction.ASC, "_id")));
89+
90+
if (hasLimit(pInfo)) {
91+
ops.add(Aggregation.limit(pInfo.getLimit()));
92+
}
93+
94+
Aggregation aggregation = Aggregation.newAggregation(ops);
95+
AggregationResults<Submodel> results =
96+
mongoOperations.aggregate(aggregation, collectionName, Submodel.class);
97+
98+
List<Submodel> submodels = results.getMappedResults();
99+
100+
String nextCursor = submodels.isEmpty()
101+
? null
102+
: submodels.get(submodels.size() - 1).getId();
103+
104+
return new CursorResult<>(nextCursor, submodels);
105+
}
106+
77107
@Override
78108
public CursorResult<List<SubmodelElement>> getSubmodelElements(String submodelId, PaginationInfo pInfo) throws ElementDoesNotExistException {
79109
List<AggregationOperation> ops = new ArrayList<>();
80110

81111
ops.add(Aggregation.match(Criteria.where("_id").is(submodelId)));
82112

83-
if (pInfo.getCursor() != null && !pInfo.getCursor().isEmpty()) {
113+
if (hasCursor(pInfo)) {
84114
Document addCursorIndex = new Document("$addFields",
85115
new Document("cursorIndex", new Document("$cond", Arrays.asList(new Document("$eq", Arrays.asList(new Document("$indexOfArray", Arrays.asList("$" + SUBMODEL_ELEMENTS_KEY + ".idShort", pInfo.getCursor())), -1)), 0,
86116
new Document("$add", Arrays.asList(new Document("$indexOfArray", Arrays.asList("$" + SUBMODEL_ELEMENTS_KEY + ".idShort", pInfo.getCursor())), 1))))));
87117
ops.add(context -> addCursorIndex);
88118

89-
int limit = (pInfo.getLimit() != null && pInfo.getLimit() > 0) ? pInfo.getLimit() : Integer.MAX_VALUE;
119+
int limit = hasLimit(pInfo) ? pInfo.getLimit() : Integer.MAX_VALUE;
90120

91121
Document projectSlice = new Document("$project", new Document(SUBMODEL_ELEMENTS_KEY, new Document("$slice", Arrays.asList("$" + SUBMODEL_ELEMENTS_KEY, "$cursorIndex", limit))));
92122
ops.add(context -> projectSlice);
93123
} else {
94-
if (pInfo.getLimit() != null && pInfo.getLimit() > 0) {
124+
if (hasLimit(pInfo)) {
95125
Document projectSlice = new Document("$project", new Document(SUBMODEL_ELEMENTS_KEY, new Document("$slice", Arrays.asList("$" + SUBMODEL_ELEMENTS_KEY, 0, pInfo.getLimit()))));
96126
ops.add(context -> projectSlice);
97127
}
@@ -296,4 +326,12 @@ private boolean existsSubmodelElement(String submodelId, String idShortPath){
296326
}
297327
}
298328

329+
private static boolean hasLimit(PaginationInfo pInfo) {
330+
return pInfo.getLimit() != null && pInfo.getLimit() > 0;
331+
}
332+
333+
private static boolean hasCursor(PaginationInfo pInfo) {
334+
return pInfo.getCursor() != null && !pInfo.getCursor().isEmpty();
335+
}
336+
299337
}

basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/SubmodelOperations.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@
4242
*/
4343
public interface SubmodelOperations {
4444

45+
/**
46+
* Retrieves all Submodels with pagination support.
47+
*
48+
* @param pInfo the pagination information
49+
* @return a {@code CursorResult} containing a list of Submodels
50+
*/
51+
CursorResult<List<Submodel>> getSubmodels(String semanticId, PaginationInfo pInfo);
52+
4553
/**
4654
* Retrieves all Submodel Elements for the given Submodel.
4755
*

0 commit comments

Comments
 (0)