Skip to content

Commit b881c90

Browse files
authored
Fixes #2941 Adds support for query builder reports (#2942)
* Fixes #2941 - Adding support for QueryBuilder queries in report builder
1 parent 06d2c1c commit b881c90

File tree

5 files changed

+427
-184
lines changed

5 files changed

+427
-184
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com)
99
<!-- Keep this up to date! After a release, change the tag name to the latest release -->
1010
[unreleased changes details]: https://github.com/Adobe-Consulting-Services/acs-aem-commons/compare/acs-aem-commons-5.0.14...HEAD
1111

12+
- #2941 - Add Query Builder support in Report Builder
13+
1214
## 5.3.4 - 2022-08-22
1315

1416
### Added

bundle/src/main/java/com/adobe/acs/commons/reports/models/QueryReportExecutor.java

Lines changed: 202 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.stream.Stream;
3535
import java.util.stream.StreamSupport;
3636

37+
import javax.inject.Inject;
3738
import javax.jcr.Node;
3839
import javax.jcr.NodeIterator;
3940
import javax.jcr.RepositoryException;
@@ -45,165 +46,248 @@
4546
import javax.jcr.query.Row;
4647
import javax.jcr.query.RowIterator;
4748

48-
import com.adobe.acs.commons.reports.api.ReportException;
49-
import com.adobe.acs.commons.reports.api.ReportExecutor;
50-
import com.adobe.acs.commons.reports.api.ResultsPage;
51-
import com.github.jknack.handlebars.Handlebars;
52-
import com.github.jknack.handlebars.Template;
53-
5449
import org.apache.commons.lang.StringEscapeUtils;
5550
import org.apache.commons.lang.StringUtils;
51+
import org.apache.commons.lang3.tuple.ImmutablePair;
52+
import org.apache.commons.lang3.tuple.Pair;
5653
import org.apache.sling.api.SlingHttpServletRequest;
5754
import org.apache.sling.api.resource.Resource;
5855
import org.apache.sling.api.resource.ResourceResolver;
5956
import org.apache.sling.models.annotations.Model;
57+
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
58+
import org.apache.sling.models.annotations.injectorspecific.Self;
6059
import org.slf4j.Logger;
6160
import org.slf4j.LoggerFactory;
6261

62+
import com.adobe.acs.commons.reports.api.ReportException;
63+
import com.adobe.acs.commons.reports.api.ReportExecutor;
64+
import com.adobe.acs.commons.reports.api.ResultsPage;
65+
import com.adobe.acs.commons.util.ParameterUtil;
66+
import com.adobe.acs.commons.util.impl.QueryHelperImpl;
67+
import com.day.cq.search.PredicateGroup;
68+
import com.day.cq.search.QueryBuilder;
69+
import com.day.cq.search.result.SearchResult;
70+
import com.github.jknack.handlebars.Handlebars;
71+
import com.github.jknack.handlebars.Template;
72+
6373
/**
6474
* Model for executing report requests.
6575
*/
6676
@Model(adaptables = SlingHttpServletRequest.class)
6777
public class QueryReportExecutor implements ReportExecutor {
6878

69-
private static final Logger log = LoggerFactory.getLogger(QueryReportExecutor.class);
79+
private static final Logger log = LoggerFactory.getLogger(QueryReportExecutor.class);
7080

71-
private QueryReportConfig config;
81+
private QueryReportConfig config;
7282

73-
private int page;
83+
private int page;
7484

75-
private SlingHttpServletRequest request;
85+
private final SlingHttpServletRequest request;
86+
private final QueryBuilder queryBuilder;
7687

77-
private String statement;
88+
@Inject
89+
public QueryReportExecutor(@Self SlingHttpServletRequest request, @OSGiService QueryBuilder queryBuilder) {
90+
this.request = request;
91+
this.queryBuilder = queryBuilder;
92+
}
93+
94+
private ResultsPage fetchResults(int limit, int offset) throws ReportException {
95+
96+
try {
97+
final Pair<Stream<Resource>, Long> results;
98+
if (isQueryBuilder()) {
99+
results = getResultsFromQueryBuilder(limit, offset);
100+
} else {
101+
results = getResultsFromQuery(limit, offset);
102+
}
103+
return new ResultsPage(results.getLeft(), config.getPageSize(), page, results.getRight());
104+
} catch (RepositoryException re) {
105+
throw new ReportException("Exception executing search results", re);
106+
}
107+
}
78108

79-
public QueryReportExecutor(SlingHttpServletRequest request) {
80-
this.request = request;
81-
}
109+
private boolean isQueryBuilder() {
110+
return QueryHelperImpl.QUERY_BUILDER.equals(config.getQueryLanguage());
111+
}
112+
113+
private Session getSession() throws ReportException {
114+
return Optional.ofNullable(request.getResourceResolver().adaptTo(Session.class))
115+
.orElseThrow(() -> new ReportException("Failed to get JCR Session"));
116+
}
82117

83-
private ResultsPage fetchResults(int limit, int offset) throws ReportException {
84-
prepareStatement();
85-
try {
86-
ResourceResolver resolver = request.getResourceResolver();
87-
QueryManager queryMgr = Optional.ofNullable(resolver.adaptTo(Session.class))
88-
.orElseThrow(() -> new ReportException("Failed to get JCR Session")).getWorkspace().getQueryManager();
118+
private com.day.cq.search.Query prepareQueryBuilderQuery() throws ReportException {
89119

90-
Query query = queryMgr.createQuery(statement, config.getQueryLanguage());
120+
Session session = getSession();
121+
final Map<String, String> params = ParameterUtil.toMap(prepareStatement().split("\n\n?"), "=", false, null,
122+
true);
91123

92-
if (page != -1) {
93-
log.debug("Fetching results with limit {} and offset {}", limit, offset);
94-
query.setLimit(limit);
95-
query.setOffset(offset);
96-
} else {
97-
log.debug("Fetching all results");
98-
}
99-
QueryResult result = query.execute();
124+
return queryBuilder.createQuery(PredicateGroup.create(params),
125+
session);
126+
}
100127

101-
NodeIterator nodes = result.getNodes();
128+
private Pair<Stream<Resource>, Long> getResultsFromQueryBuilder(
129+
int limit, int offset) throws ReportException {
102130

103-
Spliterator<Node> spliterator = Spliterators.spliteratorUnknownSize(nodes,
104-
Spliterator.ORDERED | Spliterator.NONNULL);
131+
com.day.cq.search.Query query = prepareQueryBuilderQuery();
132+
if (page != -1) {
133+
log.debug("Fetching results with limit {} and offset {}", limit, offset);
134+
query.setHitsPerPage(limit);
135+
query.setStart(offset);
136+
} else {
137+
log.debug("Fetching all results");
138+
}
105139

106-
Stream<Resource> results = StreamSupport.stream(spliterator, false).map(n -> getResource(n, resolver));
140+
SearchResult result = query.getResult();
141+
long count = -1;
142+
if (limit <= 100) {
143+
count = result.getHits().size();
144+
}
145+
ResourceResolver resolver = this.request.getResourceResolver();
146+
return ImmutablePair.of(result.getHits().stream().map(h -> {
147+
try {
148+
return resolver.getResource(h.getPath());
149+
} catch (RepositoryException e) {
150+
log.warn("Could not get node behind search result hit", e);
151+
return null;
152+
}
153+
}), count);
107154

108-
return new ResultsPage(results, config.getPageSize(), page, nodes.getSize());
109-
} catch (RepositoryException re) {
110-
throw new ReportException("Exception executing search results", re);
111155
}
112-
}
113156

114-
@Override
115-
public ResultsPage getAllResults() throws ReportException {
116-
return fetchResults(Integer.MAX_VALUE, 0);
117-
}
157+
private Pair<Stream<Resource>, Long> getResultsFromQuery(int limit, int offset)
158+
throws RepositoryException, ReportException {
159+
String statement = prepareStatement();
160+
QueryManager queryMgr = getSession().getWorkspace().getQueryManager();
161+
Query query = queryMgr.createQuery(statement, config.getQueryLanguage());
162+
163+
if (page != -1) {
164+
log.debug("Fetching results with limit {} and offset {}", limit, offset);
165+
query.setLimit(limit);
166+
query.setOffset(offset);
167+
} else {
168+
log.debug("Fetching all results");
169+
}
170+
QueryResult result = query.execute();
171+
172+
NodeIterator nodes = result.getNodes();
118173

119-
@Override
120-
public String getDetails() throws ReportException {
121-
Map<String, String> details = new LinkedHashMap<>();
122-
details.put("Language", config.getQueryLanguage());
123-
details.put("Page", Integer.toString(page));
124-
details.put("Page Size", Integer.toString(config.getPageSize()));
125-
details.put("Query", statement);
174+
Spliterator<Node> spliterator = Spliterators.spliteratorUnknownSize(nodes,
175+
Spliterator.ORDERED | Spliterator.NONNULL);
126176

127-
try {
128-
final QueryManager queryManager = Optional.ofNullable(request.getResourceResolver().adaptTo(Session.class))
129-
.orElseThrow(() -> new ReportException("Failed to get JCR Session")).getWorkspace().getQueryManager();
130-
final Query query = queryManager.createQuery("explain " + statement, config.getQueryLanguage());
131-
final QueryResult queryResult = query.execute();
177+
ResourceResolver resolver = request.getResourceResolver();
178+
return ImmutablePair.of(StreamSupport.stream(spliterator, false).map(n -> getResource(n, resolver)),
179+
nodes.getSize());
132180

133-
final RowIterator rows = queryResult.getRows();
134-
while (rows.hasNext()) {
135-
final Row row = rows.nextRow();
181+
}
136182

137-
String[] cols = queryResult.getColumnNames();
138-
Value[] values = row.getValues();
183+
@Override
184+
public ResultsPage getAllResults() throws ReportException {
185+
return fetchResults(Integer.MAX_VALUE, 0);
186+
}
139187

140-
for (int i = 0; i < cols.length; i++) {
141-
details.put(cols[i], values[i].getString());
188+
private void addQueryDetails(Map<String, String> details) throws ReportException {
189+
try {
190+
final QueryManager queryManager = getSession().getWorkspace()
191+
.getQueryManager();
192+
final Query query = queryManager.createQuery("explain " + prepareStatement(), config.getQueryLanguage());
193+
final QueryResult queryResult = query.execute();
194+
195+
final RowIterator rows = queryResult.getRows();
196+
while (rows.hasNext()) {
197+
final Row row = rows.nextRow();
198+
199+
String[] cols = queryResult.getColumnNames();
200+
Value[] values = row.getValues();
201+
202+
for (int i = 0; i < cols.length; i++) {
203+
details.put(cols[i], values[i].getString());
204+
}
205+
}
206+
} catch (RepositoryException re) {
207+
throw new ReportException("Exception getting details", re);
142208
}
143-
}
209+
}
144210

145-
} catch (RepositoryException re) {
146-
throw new ReportException("Exception getting details", re);
211+
private void addQueryBuilderDetails(Map<String, String> details) throws ReportException {
212+
SearchResult result = prepareQueryBuilderQuery().getResult();
213+
details.put("XPath Query", result.getQueryStatement());
214+
details.put("Filtering Predicates", result.getFilteringPredicates());
147215
}
148-
StringBuilder sb = new StringBuilder();
149-
for (Entry<String, String> entry : details.entrySet()) {
150-
sb.append("<dt>" + StringEscapeUtils.escapeHtml(entry.getKey()) + "</dt>");
151-
sb.append("<dd>" + StringEscapeUtils.escapeHtml(entry.getValue()) + "</dd>");
216+
217+
@Override
218+
public String getDetails() throws ReportException {
219+
String statement = prepareStatement();
220+
Map<String, String> details = new LinkedHashMap<>();
221+
details.put("Language", config.getQueryLanguage());
222+
details.put("Page", Integer.toString(page));
223+
details.put("Page Size", Integer.toString(config.getPageSize()));
224+
details.put("Query", statement);
225+
226+
if (isQueryBuilder()) {
227+
addQueryBuilderDetails(details);
228+
} else {
229+
addQueryDetails(details);
230+
}
231+
StringBuilder sb = new StringBuilder();
232+
for (Entry<String, String> entry : details.entrySet()) {
233+
sb.append("<dt>" + StringEscapeUtils.escapeHtml(entry.getKey()) + "</dt>");
234+
sb.append("<dd>" + StringEscapeUtils.escapeHtml(entry.getValue()) + "</dd>");
235+
}
236+
237+
return "<dl>" + sb.toString() + "</dl>";
152238
}
153239

154-
return "<dl>" + sb.toString() + "</dl>";
155-
}
240+
@Override
241+
public String getParameters() throws ReportException {
242+
List<String> params = new ArrayList<>();
243+
Enumeration<String> keys = request.getParameterNames();
244+
while (keys.hasMoreElements()) {
245+
String key = keys.nextElement();
246+
for (String value : request.getParameterValues(key)) {
247+
try {
248+
params.add(URLEncoder.encode(key, "UTF-8") + "=" + URLEncoder.encode(value, "UTF-8"));
249+
} catch (UnsupportedEncodingException e) {
250+
throw new ReportException("UTF-8 encoding available", e);
251+
}
252+
}
253+
}
254+
return StringUtils.join(params, "&");
255+
}
156256

157-
@Override
158-
public String getParameters() throws ReportException {
159-
List<String> params = new ArrayList<>();
160-
Enumeration<String> keys = request.getParameterNames();
161-
while (keys.hasMoreElements()) {
162-
String key = keys.nextElement();
163-
for (String value : request.getParameterValues(key)) {
257+
private Resource getResource(Node node, ResourceResolver resolver) {
164258
try {
165-
params.add(URLEncoder.encode(key, "UTF-8") + "=" + URLEncoder.encode(value, "UTF-8"));
166-
} catch (UnsupportedEncodingException e) {
167-
throw new ReportException("UTF-8 encoding available", e);
259+
return resolver.getResource(node.getPath());
260+
} catch (RepositoryException e) {
261+
log.warn("Failed to get path from node: {}", node, e);
262+
return null;
168263
}
169-
}
170-
}
171-
return StringUtils.join(params, "&");
172-
}
173-
174-
private Resource getResource(Node node, ResourceResolver resolver) {
175-
try {
176-
return resolver.getResource(node.getPath());
177-
} catch (RepositoryException e) {
178-
log.warn("Failed to get path from node: {}", node, e);
179-
return null;
180-
}
181-
}
182-
183-
@Override
184-
public ResultsPage getResults() throws ReportException {
185-
return fetchResults(config.getPageSize(), config.getPageSize() * page);
186-
}
187-
188-
private void prepareStatement() throws ReportException {
189-
try {
190-
Map<String, String> parameters = getParamPatternMap(request);
191-
Template template = new Handlebars().compileInline(config.getQuery());
192-
statement = template.apply(parameters);
193-
log.trace("Loaded statement: {}", statement);
194-
} catch (IOException ioe) {
195-
throw new ReportException("Exception templating query", ioe);
196-
}
197-
}
198-
199-
@Override
200-
public void setConfiguration(Resource config) {
201-
this.config = config.adaptTo(QueryReportConfig.class);
202-
}
203-
204-
@Override
205-
public void setPage(int page) {
206-
this.page = page;
207-
}
264+
}
265+
266+
@Override
267+
public ResultsPage getResults() throws ReportException {
268+
return fetchResults(config.getPageSize(), config.getPageSize() * page);
269+
}
270+
271+
private String prepareStatement() throws ReportException {
272+
try {
273+
Map<String, String> parameters = getParamPatternMap(request);
274+
Template template = new Handlebars().compileInline(config.getQuery());
275+
String statement = template.apply(parameters);
276+
log.trace("Loaded statement: {}", statement);
277+
return statement;
278+
} catch (IOException ioe) {
279+
throw new ReportException("Exception templating query", ioe);
280+
}
281+
}
282+
283+
@Override
284+
public void setConfiguration(Resource config) {
285+
this.config = config.adaptTo(QueryReportConfig.class);
286+
}
287+
288+
@Override
289+
public void setPage(int page) {
290+
this.page = page;
291+
}
208292

209293
}

0 commit comments

Comments
 (0)