Skip to content

Commit 2897b41

Browse files
committed
Merge remote-tracking branch 'origin/feature-tree-table' into avoidRename
2 parents 7b6179e + c7f50a8 commit 2897b41

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package my.bookshop.handlers;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.Comparator;
5+
import java.util.Deque;
6+
import java.util.HashMap;
7+
import java.util.HashSet;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Set;
11+
import java.util.stream.Collectors;
12+
13+
import org.springframework.context.annotation.Profile;
14+
import org.springframework.stereotype.Component;
15+
16+
import com.sap.cds.Row;
17+
import com.sap.cds.ql.CQL;
18+
import com.sap.cds.ql.Select;
19+
import com.sap.cds.ql.cqn.CqnElementRef;
20+
import com.sap.cds.ql.cqn.CqnPredicate;
21+
import com.sap.cds.ql.cqn.CqnSelect;
22+
import com.sap.cds.ql.cqn.CqnValue;
23+
import com.sap.cds.ql.cqn.Modifier;
24+
import com.sap.cds.ql.cqn.transformation.CqnTopLevelsTransformation;
25+
import com.sap.cds.ql.cqn.transformation.CqnAncestorsTransformation;
26+
import com.sap.cds.ql.cqn.transformation.CqnDescendantsTransformation;
27+
import com.sap.cds.ql.cqn.transformation.CqnFilterTransformation;
28+
import com.sap.cds.ql.cqn.transformation.CqnSearchTransformation;
29+
import com.sap.cds.ql.cqn.transformation.CqnOrderByTransformation;
30+
import com.sap.cds.ql.cqn.transformation.CqnTransformation;
31+
import com.sap.cds.services.cds.CdsReadEventContext;
32+
import com.sap.cds.services.cds.CqnService;
33+
import com.sap.cds.services.handler.EventHandler;
34+
import com.sap.cds.services.handler.annotations.Before;
35+
import com.sap.cds.services.handler.annotations.ServiceName;
36+
import com.sap.cds.services.persistence.PersistenceService;
37+
38+
import cds.gen.adminservice.AdminService_;
39+
import cds.gen.adminservice.GenreHierarchy;
40+
import cds.gen.adminservice.GenreHierarchy_;
41+
42+
@Component
43+
@Profile("default")
44+
@ServiceName(AdminService_.CDS_NAME)
45+
public class HierarchyHandler implements EventHandler {
46+
47+
private final PersistenceService db;
48+
49+
HierarchyHandler(PersistenceService db) {
50+
this.db = db;
51+
}
52+
53+
@Before(event = CqnService.EVENT_READ, entity = GenreHierarchy_.CDS_NAME)
54+
public void readGenreHierarchy(CdsReadEventContext event) {
55+
List<CqnTransformation> trafos = event.getCqn().transformations();
56+
List<GenreHierarchy> result = null;
57+
58+
if (trafos.size() < 1) {
59+
return;
60+
}
61+
62+
if (getTopLevels(trafos) instanceof CqnTopLevelsTransformation topLevels) {
63+
result = topLevels(topLevels, CQL.TRUE);
64+
} else if (trafos.get(0) instanceof CqnDescendantsTransformation descendants) {
65+
result = handleDescendants(descendants);
66+
} else if (trafos.get(0) instanceof CqnAncestorsTransformation ancestors) {
67+
if (trafos.size() == 2 && trafos.get(1) instanceof CqnTopLevelsTransformation topLevels) {
68+
result = handleAncestors(ancestors, topLevels);
69+
} else if (trafos.size() == 3 && trafos.get(2) instanceof CqnTopLevelsTransformation topLevels) {
70+
result = handleAncestors(ancestors, topLevels);
71+
}
72+
}
73+
74+
setResult(event, result);
75+
}
76+
77+
private CqnTopLevelsTransformation getTopLevels(List<CqnTransformation> trafos) {
78+
if (trafos.get(0) instanceof CqnTopLevelsTransformation topLevels) {
79+
return topLevels;
80+
} else if (trafos.size() == 2 && trafos.get(0) instanceof CqnOrderByTransformation && trafos.get(1) instanceof CqnTopLevelsTransformation topLevels) {
81+
return topLevels;
82+
}
83+
return null;
84+
}
85+
86+
private void setResult(CdsReadEventContext event, List<GenreHierarchy> result) {
87+
if (!result.isEmpty()) {
88+
addDrillState(result);
89+
}
90+
91+
event.setResult(result);
92+
}
93+
94+
private void addDrillState(List<GenreHierarchy> ghs) {
95+
List<Integer> ids = ghs.stream().map(gh -> gh.getId()).toList();
96+
Set<Integer> parents = ghs.stream().map(gh -> gh.getParntId()).filter(p -> p != null).collect(Collectors.toSet());
97+
CqnSelect q = Select.from(AdminService_.GENRE_HIERARCHY).columns(gh -> gh.parnt_ID().as("id"))
98+
.where(gh -> gh.parnt_ID().in(ids));
99+
Set<Object> nonLeafs = db
100+
.run(q)
101+
.stream().map(r -> r.get("id")).collect(Collectors.toSet());
102+
103+
for (GenreHierarchy gh : ghs) {
104+
Integer id = gh.getId();
105+
if (nonLeafs.contains(id)) {
106+
if (parents.contains(id)) {
107+
gh.setDrillState("expanded");
108+
} else {
109+
gh.setDrillState("collapsed");
110+
}
111+
} else {
112+
gh.setDrillState("leaf");
113+
}
114+
}
115+
}
116+
117+
private List<GenreHierarchy> handleDescendants(CqnDescendantsTransformation descendants) {
118+
Map<Integer, GenreHierarchy> lookup = new HashMap<>();
119+
CqnFilterTransformation filter = (CqnFilterTransformation) descendants.transformations().get(0);
120+
CqnSelect getRoot = Select.from(AdminService_.GENRE_HIERARCHY).where(filter.filter());
121+
GenreHierarchy root = db.run(getRoot).single(GenreHierarchy.class);
122+
lookup.put(root.getId(), root);
123+
124+
CqnPredicate parentFilter = CQL.copy(filter.filter(), new Modifier() {
125+
@Override
126+
public CqnValue ref(CqnElementRef ref) {
127+
return CQL.get(GenreHierarchy.PARNT_ID);
128+
}
129+
});
130+
131+
CqnSelect childrenCQN = Select.from(AdminService_.GENRE_HIERARCHY).where(parentFilter);
132+
List<GenreHierarchy> children = db.run(childrenCQN).listOf(GenreHierarchy.class);
133+
children.forEach(gh -> lookup.put(gh.getId(), gh));
134+
children.forEach(gh -> gh.setParnt(lookup.get(gh.getParntId())));
135+
136+
return children.stream().sorted(new Sorter()).toList();
137+
}
138+
139+
private List<GenreHierarchy> handleAncestors(CqnAncestorsTransformation ancestors, CqnTopLevelsTransformation topLevels) {
140+
CqnTransformation trafo = ancestors.transformations().get(0);
141+
Select<GenreHierarchy_> inner = Select.from(AdminService_.GENRE_HIERARCHY).columns(gh -> gh.ID());
142+
if (trafo instanceof CqnFilterTransformation filter) {
143+
inner.where(filter.filter());
144+
} else if (trafo instanceof CqnSearchTransformation search) {
145+
inner.search(search.search());
146+
}
147+
Select<GenreHierarchy_> outer = Select.from(AdminService_.GENRE_HIERARCHY).columns(gh -> gh.ID().as("i0"), gh -> gh.parnt().ID().as("i1"),
148+
gh -> gh.parnt().parnt().ID().as("i2"), gh -> gh.parnt().parnt().parnt().ID().as("i3"),
149+
gh -> gh.parnt().parnt().parnt().parnt().ID().as("i4")).where(gh -> gh.ID().in(inner));
150+
151+
Set<Integer> ancestorIds = new HashSet<>();
152+
db.run(outer).stream().forEach(r -> {
153+
addIfNotNull(ancestorIds, r, "i0");
154+
addIfNotNull(ancestorIds, r, "i1");
155+
addIfNotNull(ancestorIds, r, "i2");
156+
addIfNotNull(ancestorIds, r, "i3");
157+
addIfNotNull(ancestorIds, r, "i4");
158+
});
159+
160+
CqnPredicate filter = CQL.get(GenreHierarchy_.ID).in(ancestorIds.stream().toList());
161+
return topLevels(topLevels, filter);
162+
}
163+
164+
private void addIfNotNull(Set<Integer> ancestorIds, Row r, String key) {
165+
Integer id = (Integer) r.get(key);
166+
if (id != null) {
167+
ancestorIds.add(id);
168+
}
169+
}
170+
171+
private List<GenreHierarchy> topLevels(CqnTopLevelsTransformation topLevels, CqnPredicate filter) {
172+
return topLevels.levels() < 0 || !(topLevels.expandLevels().isEmpty()) ? topLevelsAll(filter) : topLevelsLimit(topLevels.levels(), filter);
173+
}
174+
175+
private List<GenreHierarchy> topLevelsLimit(long limit, CqnPredicate filter) {
176+
Map <Integer, GenreHierarchy> lookup = new HashMap<>();
177+
178+
CqnSelect getRoots = Select.from(AdminService_.GENRE_HIERARCHY).where(gh -> gh.parnt_ID().isNull().and(filter));
179+
List<GenreHierarchy> roots = db.run(getRoots).listOf(GenreHierarchy.class);
180+
roots.forEach(root -> {
181+
root.setDistanceFromRoot(0l);
182+
lookup.put(root.getId(), root);
183+
List<Integer> parents = List.of(root.getId());
184+
for (long i = 1; i < limit; i++) {
185+
List<Integer> ps = parents;
186+
CqnSelect getChildren = Select.from(AdminService_.GENRE_HIERARCHY).where(gh -> gh.parnt_ID().in(ps).and(filter));
187+
List<GenreHierarchy> children = db.run(getChildren).listOf(GenreHierarchy.class);
188+
if (children.isEmpty()) {
189+
break;
190+
}
191+
long dfr = i;
192+
parents = children.stream().peek(gh -> {
193+
gh.setParnt(lookup.get(gh.getParntId()));
194+
gh.setDistanceFromRoot(dfr);
195+
lookup.put(gh.getId(), gh);
196+
}).map(GenreHierarchy::getId).toList();
197+
}
198+
});
199+
200+
return lookup.values().stream().sorted(new Sorter()).toList();
201+
}
202+
203+
private List<GenreHierarchy> topLevelsAll(CqnPredicate filter) {
204+
Map<Integer, GenreHierarchy> lookup = new HashMap<>();
205+
206+
CqnSelect allCqn = Select.from(AdminService_.GENRE_HIERARCHY).where(filter);
207+
var all = db.run(allCqn).listOf(GenreHierarchy.class);
208+
all.forEach(gh -> lookup.put(gh.getId(), gh));
209+
all.forEach(gh -> gh.setParnt(lookup.get(gh.getParntId())));
210+
all.forEach(gh -> gh.setDistanceFromRoot(distanceFromRoot(gh)));
211+
212+
return all.stream().sorted(new Sorter()).toList();
213+
}
214+
215+
private static long distanceFromRoot(GenreHierarchy gh) {
216+
long dfr = 0;
217+
while (gh.getParnt() != null) {
218+
dfr++;
219+
gh = gh.getParnt();
220+
}
221+
222+
return dfr;
223+
}
224+
225+
private class Sorter implements Comparator<GenreHierarchy> {
226+
227+
@Override
228+
public int compare(GenreHierarchy gh1, GenreHierarchy gh2) {
229+
Deque<String> path1 = getPath(gh1);
230+
Deque<String> path2 = getPath(gh2);
231+
int res = 0;
232+
233+
while (!path1.isEmpty() && !path2.isEmpty()) {
234+
String last1 = path1.pop();
235+
String last2 = path2.pop();
236+
res = last1.compareTo(last2);
237+
if (res != 0) {
238+
return res;
239+
}
240+
}
241+
return res;
242+
}
243+
244+
Deque<String> getPath(GenreHierarchy gh){
245+
Deque<String> path = new ArrayDeque<>();
246+
do {
247+
path.push(gh.getName());
248+
gh = gh.getParnt();
249+
} while (gh != null);
250+
251+
return path;
252+
}
253+
}
254+
}

0 commit comments

Comments
 (0)