Skip to content

Commit 6d460cd

Browse files
committed
MID-10990 Optimize AccessCertificationWorkItem export for large datasets
1 parent ade02e0 commit 6d460cd

5 files changed

Lines changed: 646 additions & 78 deletions

File tree

gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/page/admin/certification/column/CertItemObjectDisplayNameColumn.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,16 @@ public IModel<String> getDataModel(IModel<PrismContainerValueWrapper<AccessCerti
5858

5959
private String getObjectDisplayName(IModel<PrismContainerValueWrapper<AccessCertificationWorkItemType>> rowModel) {
6060
AccessCertificationCaseType certCase = CertCampaignTypeUtil.getCase(unwrapRowModel(rowModel));
61-
PrismObject<?> resolvedObject = WebModelServiceUtils.loadObject(certCase.getObjectRef(), null, context.getPageBase());
62-
return WebComponentUtil.getDisplayName(resolvedObject, true);
61+
if (certCase == null || certCase.getObjectRef() == null) {
62+
return "";
63+
}
64+
// Use pre-loaded object from reference if available (set by beforeTransformation optimization)
65+
PrismObject<?> resolvedObject = certCase.getObjectRef().getObject();
66+
if (resolvedObject == null) {
67+
// Fallback to loading object (for non-optimized paths)
68+
resolvedObject = WebModelServiceUtils.loadObject(certCase.getObjectRef(), null, context.getPageBase());
69+
}
70+
return WebComponentUtil.getDisplayName(resolvedObject, true);
6371
}
6472
};
6573
}

gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/page/admin/certification/column/CertItemTargetDisplayNameColumn.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,17 @@ public IModel<String> getDataModel(IModel<PrismContainerValueWrapper<AccessCerti
5656

5757
private String getTargetDisplayName(IModel<PrismContainerValueWrapper<AccessCertificationWorkItemType>> rowModel) {
5858
AccessCertificationCaseType certCase = CertCampaignTypeUtil.getCase(unwrapRowModel(rowModel));
59-
PrismObject<?> resolvedObject = WebModelServiceUtils.loadObject(certCase.getTargetRef(), null, context.getPageBase());
60-
return WebComponentUtil.getDisplayName(resolvedObject, true);
59+
if (certCase == null || certCase.getTargetRef() == null) {
60+
return "";
61+
}
62+
// Use pre-loaded object from reference if available (set by beforeTransformation optimization)
63+
PrismObject<?> resolvedObject = certCase.getTargetRef().getObject();
64+
if (resolvedObject == null) {
65+
// Fallback to loading object (for non-optimized paths)
66+
resolvedObject = WebModelServiceUtils.loadObject(certCase.getTargetRef(), null, context.getPageBase());
67+
}
68+
return WebComponentUtil.getDisplayName(resolvedObject, true);
6169
}
6270
};
6371
}
6472
}
65-

repo/repo-sqale/src/main/java/com/evolveum/midpoint/repo/sqale/mapping/ReferenceNameResolver.java

Lines changed: 123 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
import java.util.*;
1010

11+
import com.evolveum.midpoint.repo.sqale.qmodel.focus.QUser;
12+
import com.evolveum.midpoint.repo.sqale.qmodel.role.MAbstractRole;
13+
import com.evolveum.midpoint.repo.sqale.qmodel.role.QAbstractRole;
1114
import com.evolveum.midpoint.repo.sqale.qmodel.shadow.QShadow;
1215
import com.evolveum.midpoint.repo.sqlbase.SqlBaseOperationTracker;
1316

@@ -32,8 +35,29 @@
3235

3336
public abstract class ReferenceNameResolver {
3437

38+
/**
39+
* Holds resolved name and display name for an object.
40+
* Display name is: fullName for Users, displayName for AbstractRoles, null otherwise.
41+
*/
42+
public record ResolvedNames(String name, String displayName) {}
43+
3544
public abstract <S> S resolve(S object, JdbcSession session);
3645

46+
/**
47+
* Batch resolve names and display names for a set of OIDs.
48+
* Results are cached and can be retrieved via {@link #getResolvedNames(UUID)}.
49+
*
50+
* @param session JDBC session
51+
* @param oids OIDs to resolve
52+
* @param includeDisplayNames if true, also resolve display names (requires LEFT JOIN)
53+
*/
54+
public abstract void batchResolve(JdbcSession session, Set<UUID> oids, boolean includeDisplayNames);
55+
56+
/**
57+
* Get cached resolved names for an OID, or null if not resolved.
58+
*/
59+
public abstract ResolvedNames getResolvedNames(UUID oid);
60+
3761
public static ReferenceNameResolver from(Collection<SelectorOptions<GetOperationOptions>> options) {
3862
@NotNull
3963
List<? extends ItemPath> paths = getPathsToResolve(options);
@@ -71,15 +95,27 @@ private static final class Noop extends ReferenceNameResolver {
7195
public <S> S resolve(S object, JdbcSession session) {
7296
return object;
7397
}
98+
99+
@Override
100+
public void batchResolve(JdbcSession session, Set<UUID> oids, boolean includeDisplayNames) {
101+
// No-op
102+
}
103+
104+
@Override
105+
public ResolvedNames getResolvedNames(UUID oid) {
106+
return null;
107+
}
74108
}
75109

76110
private static final class Impl extends ReferenceNameResolver {
77111

78112
private final List<? extends ItemPath> paths;
79-
private final Map<UUID, PolyString> uuidToName = new HashMap<>();
80113
private final Set<UUID> oidsToResolve = new HashSet<>();
81114
private final Set<UUID> shadowOidsToResolve = new HashSet<>();
82115

116+
// Unified cache for name and displayName
117+
private final Map<UUID, ResolvedNames> resolvedNamesCache = new HashMap<>();
118+
83119

84120
public Impl(List<? extends ItemPath> paths) {
85121
super();
@@ -120,30 +156,31 @@ public <S> S resolve(S object, JdbcSession session) {
120156
}
121157

122158
private void resolveNames(JdbcSession session) {
123-
124-
// TODO: Add batch processing
125159
var object = new QObject<>(MObject.class, "obj");
126160
var shadow = new QShadow("s");
127-
resolveNames(session, object, oidsToResolve);
128-
resolveNames(session, shadow, shadowOidsToResolve);
129-
161+
resolveNamesSimple(session, object, oidsToResolve);
162+
resolveNamesSimple(session, shadow, shadowOidsToResolve);
130163
}
131-
private void resolveNames(JdbcSession session, QObject<?> object, Set<UUID> oidsToResolve) {
164+
165+
/**
166+
* Simple name resolution (name only, no displayName).
167+
* Used by resolve() for references not pre-loaded by batchResolve().
168+
*/
169+
private void resolveNamesSimple(JdbcSession session, QObject<?> object, Set<UUID> oidsToResolve) {
132170
if (oidsToResolve.isEmpty()) {
133171
return;
134172
}
135173
List<Tuple> namesResult = session.newQuery()
136174
.from(object)
137-
.select(object.oid, object.nameOrig, object.nameNorm)
175+
.select(object.oid, object.nameOrig)
138176
.where(object.oid.in(oidsToResolve))
139177
.fetch();
140178
for (Tuple named : namesResult) {
141179
UUID uuid = named.get(object.oid);
142180
oidsToResolve.remove(uuid);
143-
String orig = named.get(object.nameOrig);
144-
String norm = named.get(object.nameNorm);
145-
PolyString poly = new PolyString(orig, norm);
146-
uuidToName.put(uuid, poly);
181+
String name = named.get(object.nameOrig);
182+
// Only set name, displayName is null (not requested)
183+
resolvedNamesCache.put(uuid, new ResolvedNames(name, null));
147184
}
148185
}
149186

@@ -162,10 +199,10 @@ private void initialVisit(PrismReferenceValue value) {
162199
return;
163200
}
164201
UUID oid = SqaleUtils.oidToUuid(value.getOid());
165-
PolyString maybe = uuidToName.get(oid);
166-
if (maybe != null) {
167-
value.setTargetName(maybe);
168-
} else if (QNameUtil.match(ShadowType.COMPLEX_TYPE,value.getTargetType())) {
202+
ResolvedNames resolved = resolvedNamesCache.get(oid);
203+
if (resolved != null) {
204+
value.setTargetName(PolyString.fromOrig(resolved.name()));
205+
} else if (QNameUtil.match(ShadowType.COMPLEX_TYPE, value.getTargetType())) {
169206
shadowOidsToResolve.add(oid);
170207
} else {
171208
oidsToResolve.add(oid);
@@ -176,10 +213,77 @@ private void updateReference(PrismReferenceValue value) {
176213
if (value.getOid() == null) {
177214
return;
178215
}
179-
PolyString name = uuidToName.get(SqaleUtils.oidToUuid(value.getOid()));
180-
if (name != null) {
181-
value.setTargetName(name);
216+
ResolvedNames resolved = resolvedNamesCache.get(SqaleUtils.oidToUuid(value.getOid()));
217+
if (resolved != null) {
218+
value.setTargetName(PolyString.fromOrig(resolved.name()));
219+
}
220+
}
221+
222+
@Override
223+
public void batchResolve(JdbcSession session, Set<UUID> oids, boolean includeDisplayNames) {
224+
if (oids == null || oids.isEmpty()) {
225+
return;
182226
}
227+
228+
// Filter out OIDs that are already cached
229+
Set<UUID> oidsToLoad = new HashSet<>();
230+
for (UUID oid : oids) {
231+
if (!resolvedNamesCache.containsKey(oid)) {
232+
oidsToLoad.add(oid);
233+
}
234+
}
235+
236+
if (oidsToLoad.isEmpty()) {
237+
return;
238+
}
239+
240+
if (includeDisplayNames) {
241+
// Query m_object with LEFT JOIN to m_user and m_abstract_role for display names
242+
QObject<MObject> qObj = new QObject<>(MObject.class, "obj");
243+
QUser qUser = new QUser("usr");
244+
QAbstractRole<MAbstractRole> qRole = new QAbstractRole<>(MAbstractRole.class, "ar");
245+
246+
List<Tuple> rows = session.newQuery()
247+
.select(qObj.oid, qObj.nameOrig, qUser.fullNameOrig, qRole.displayNameOrig)
248+
.from(qObj)
249+
.leftJoin(qUser).on(qObj.oid.eq(qUser.oid))
250+
.leftJoin(qRole).on(qObj.oid.eq(qRole.oid))
251+
.where(qObj.oid.in(oidsToLoad))
252+
.fetch();
253+
254+
for (Tuple row : rows) {
255+
UUID oid = row.get(qObj.oid);
256+
String fullName = row.get(qUser.fullNameOrig);
257+
String roleDisplayName = row.get(qRole.displayNameOrig);
258+
String name = row.get(qObj.nameOrig);
259+
260+
// fullName for Users, displayName for AbstractRoles, null otherwise
261+
// GUI components decide whether to fallback to name
262+
String displayName = fullName != null ? fullName : roleDisplayName;
263+
264+
resolvedNamesCache.put(oid, new ResolvedNames(name, displayName));
265+
}
266+
} else {
267+
// Simple name-only query
268+
QObject<MObject> qObj = new QObject<>(MObject.class, "obj");
269+
270+
List<Tuple> rows = session.newQuery()
271+
.select(qObj.oid, qObj.nameOrig)
272+
.from(qObj)
273+
.where(qObj.oid.in(oidsToLoad))
274+
.fetch();
275+
276+
for (Tuple row : rows) {
277+
UUID oid = row.get(qObj.oid);
278+
String name = row.get(qObj.nameOrig);
279+
resolvedNamesCache.put(oid, new ResolvedNames(name, null));
280+
}
281+
}
282+
}
283+
284+
@Override
285+
public ResolvedNames getResolvedNames(UUID oid) {
286+
return resolvedNamesCache.get(oid);
183287
}
184288
}
185289
}

0 commit comments

Comments
 (0)