|
34 | 34 | import java.util.stream.Stream; |
35 | 35 | import java.util.stream.StreamSupport; |
36 | 36 |
|
| 37 | +import javax.inject.Inject; |
37 | 38 | import javax.jcr.Node; |
38 | 39 | import javax.jcr.NodeIterator; |
39 | 40 | import javax.jcr.RepositoryException; |
|
45 | 46 | import javax.jcr.query.Row; |
46 | 47 | import javax.jcr.query.RowIterator; |
47 | 48 |
|
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 | | - |
54 | 49 | import org.apache.commons.lang.StringEscapeUtils; |
55 | 50 | import org.apache.commons.lang.StringUtils; |
| 51 | +import org.apache.commons.lang3.tuple.ImmutablePair; |
| 52 | +import org.apache.commons.lang3.tuple.Pair; |
56 | 53 | import org.apache.sling.api.SlingHttpServletRequest; |
57 | 54 | import org.apache.sling.api.resource.Resource; |
58 | 55 | import org.apache.sling.api.resource.ResourceResolver; |
59 | 56 | 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; |
60 | 59 | import org.slf4j.Logger; |
61 | 60 | import org.slf4j.LoggerFactory; |
62 | 61 |
|
| 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 | + |
63 | 73 | /** |
64 | 74 | * Model for executing report requests. |
65 | 75 | */ |
66 | 76 | @Model(adaptables = SlingHttpServletRequest.class) |
67 | 77 | public class QueryReportExecutor implements ReportExecutor { |
68 | 78 |
|
69 | | - private static final Logger log = LoggerFactory.getLogger(QueryReportExecutor.class); |
| 79 | + private static final Logger log = LoggerFactory.getLogger(QueryReportExecutor.class); |
70 | 80 |
|
71 | | - private QueryReportConfig config; |
| 81 | + private QueryReportConfig config; |
72 | 82 |
|
73 | | - private int page; |
| 83 | + private int page; |
74 | 84 |
|
75 | | - private SlingHttpServletRequest request; |
| 85 | + private final SlingHttpServletRequest request; |
| 86 | + private final QueryBuilder queryBuilder; |
76 | 87 |
|
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 | + } |
78 | 108 |
|
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 | + } |
82 | 117 |
|
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 { |
89 | 119 |
|
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); |
91 | 123 |
|
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 | + } |
100 | 127 |
|
101 | | - NodeIterator nodes = result.getNodes(); |
| 128 | + private Pair<Stream<Resource>, Long> getResultsFromQueryBuilder( |
| 129 | + int limit, int offset) throws ReportException { |
102 | 130 |
|
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 | + } |
105 | 139 |
|
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); |
107 | 154 |
|
108 | | - return new ResultsPage(results, config.getPageSize(), page, nodes.getSize()); |
109 | | - } catch (RepositoryException re) { |
110 | | - throw new ReportException("Exception executing search results", re); |
111 | 155 | } |
112 | | - } |
113 | 156 |
|
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(); |
118 | 173 |
|
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); |
126 | 176 |
|
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()); |
132 | 180 |
|
133 | | - final RowIterator rows = queryResult.getRows(); |
134 | | - while (rows.hasNext()) { |
135 | | - final Row row = rows.nextRow(); |
| 181 | + } |
136 | 182 |
|
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 | + } |
139 | 187 |
|
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); |
142 | 208 | } |
143 | | - } |
| 209 | + } |
144 | 210 |
|
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()); |
147 | 215 | } |
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>"; |
152 | 238 | } |
153 | 239 |
|
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 | + } |
156 | 256 |
|
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) { |
164 | 258 | 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; |
168 | 263 | } |
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 | + } |
208 | 292 |
|
209 | 293 | } |
0 commit comments