1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.client.console.panels.search;
20
21 import java.io.Serializable;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.function.Function;
26 import java.util.regex.Pattern;
27 import java.util.stream.Collectors;
28 import org.apache.commons.collections4.BidiMap;
29 import org.apache.commons.collections4.bidimap.DualHashBidiMap;
30 import org.apache.commons.lang3.BooleanUtils;
31 import org.apache.commons.lang3.StringUtils;
32 import org.apache.commons.lang3.math.NumberUtils;
33 import org.apache.cxf.jaxrs.ext.search.ConditionType;
34 import org.apache.cxf.jaxrs.ext.search.SearchBean;
35 import org.apache.cxf.jaxrs.ext.search.SearchCondition;
36 import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
37 import org.apache.syncope.common.lib.search.AbstractFiqlSearchConditionBuilder;
38 import org.apache.syncope.common.lib.search.AnyObjectFiqlSearchConditionBuilder;
39 import org.apache.syncope.common.lib.search.GroupFiqlSearchConditionBuilder;
40 import org.apache.syncope.common.lib.search.SpecialAttr;
41 import org.apache.syncope.common.lib.search.SyncopeFiqlParser;
42 import org.apache.syncope.common.lib.search.SyncopeFiqlSearchCondition;
43 import org.apache.syncope.common.lib.search.SyncopeProperty;
44 import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder;
45 import org.apache.syncope.common.lib.to.PlainSchemaTO;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 public final class SearchUtils implements Serializable {
50
51 private static final long serialVersionUID = 398381905376547084L;
52
53 public static final Function<SearchClause, CompleteCondition> NO_CUSTOM_CONDITION = clause -> null;
54
55 private static final Logger LOG = LoggerFactory.getLogger(SearchUtils.class);
56
57 private static final BidiMap<String, String> ENCODINGS = new DualHashBidiMap<>() {
58
59 private static final long serialVersionUID = 5636572627689425575L;
60
61 {
62 put(",", "%252C");
63 put(";", "%253B");
64 put("+", "%252B");
65 }
66 };
67
68 public static Pattern getTypeConditionPattern(final String type) {
69 return Pattern.compile(String.format(";\\$type==%s|\\$type==%s;", type, type));
70 }
71
72 public static Map<String, List<SearchClause>> getSearchClauses(final Map<String, String> fiql) {
73 return fiql.entrySet().stream().collect(Collectors.toMap(
74 Map.Entry::getKey,
75 e -> getSearchClauses(e.getValue().replaceAll(getTypeConditionPattern(e.getKey()).pattern(), ""))));
76 }
77
78 public static List<SearchClause> getSearchClauses(final String fiql) {
79 List<SearchClause> clauses = new ArrayList<>();
80 if (StringUtils.isNotBlank(fiql)) {
81 try {
82 SyncopeFiqlParser<SearchBean> fiqlParser = new SyncopeFiqlParser<>(
83 SearchBean.class, AbstractFiqlSearchConditionBuilder.CONTEXTUAL_PROPERTIES);
84 clauses.addAll(getSearchClauses(fiqlParser.parse(fiql)));
85 } catch (Exception e) {
86 LOG.error("Unparseable FIQL expression '{}'", fiql, e);
87 }
88 }
89 return clauses;
90 }
91
92 private static List<SearchClause> getSearchClauses(final SearchCondition<SearchBean> sc) {
93 List<SearchClause> clauses = new ArrayList<>();
94
95 if (sc.getStatement() == null) {
96 clauses.addAll(getCompoundSearchClauses(sc));
97 } else {
98 clauses.add(getPrimitiveSearchClause(sc));
99 }
100
101 return clauses;
102 }
103
104 private static List<SearchClause> getCompoundSearchClauses(final SearchCondition<SearchBean> sc) {
105 List<SearchClause> clauses = new ArrayList<>();
106
107 sc.getSearchConditions().forEach(searchCondition -> {
108 if (searchCondition.getStatement() == null) {
109 clauses.addAll(getCompoundSearchClauses(searchCondition));
110 } else {
111 SearchClause clause = getPrimitiveSearchClause(searchCondition);
112 if (sc.getConditionType() == ConditionType.AND) {
113 clause.setOperator(SearchClause.Operator.AND);
114 }
115 if (sc.getConditionType() == ConditionType.OR) {
116 clause.setOperator(SearchClause.Operator.OR);
117 }
118 clauses.add(clause);
119 }
120 });
121
122 return clauses;
123 }
124
125 private static SearchClause getPrimitiveSearchClause(final SearchCondition<SearchBean> sc) {
126 SearchClause clause = new SearchClause();
127
128 String property = sc.getCondition().getKeySet().iterator().next();
129 clause.setProperty(property);
130
131 String value = ENCODINGS.values().stream().
132 reduce(sc.getCondition().get(property), (s, v) -> s.replace(v, ENCODINGS.getKey(v)));
133 clause.setValue(value);
134
135 LOG.debug("Condition: " + sc.getCondition());
136
137 if (SpecialAttr.ROLES.toString().equals(property)) {
138 clause.setType(SearchClause.Type.ROLE_MEMBERSHIP);
139 clause.setProperty(value);
140 } else if (SpecialAttr.PRIVILEGES.toString().equals(property)) {
141 clause.setType(SearchClause.Type.PRIVILEGE);
142 clause.setProperty(value);
143 } else if (SpecialAttr.RELATIONSHIPS.toString().equals(property)) {
144 clause.setType(SearchClause.Type.RELATIONSHIP);
145 clause.setProperty(value);
146 } else if (SpecialAttr.RELATIONSHIP_TYPES.toString().equals(property)) {
147 clause.setType(SearchClause.Type.RELATIONSHIP);
148 clause.setProperty(value);
149 } else if (SpecialAttr.GROUPS.toString().equals(property)) {
150 clause.setType(SearchClause.Type.GROUP_MEMBERSHIP);
151 clause.setProperty(value);
152 } else if (SpecialAttr.AUX_CLASSES.toString().equals(property)) {
153 clause.setType(SearchClause.Type.AUX_CLASS);
154 clause.setProperty(value);
155 } else if (SpecialAttr.RESOURCES.toString().equals(property)) {
156 clause.setType(SearchClause.Type.RESOURCE);
157 clause.setProperty(value);
158 } else if (SpecialAttr.MEMBER.toString().equals(property)) {
159 clause.setType(SearchClause.Type.GROUP_MEMBER);
160 clause.setProperty(value);
161 } else if (property.startsWith("$")) {
162 clause.setType(SearchClause.Type.CUSTOM);
163 clause.setProperty(value);
164 } else {
165 clause.setType(SearchClause.Type.ATTRIBUTE);
166 }
167
168 ConditionType ct = sc.getConditionType();
169 if (sc instanceof SyncopeFiqlSearchCondition && sc.getConditionType() == ConditionType.CUSTOM) {
170 SyncopeFiqlSearchCondition<SearchBean> sfsc = (SyncopeFiqlSearchCondition<SearchBean>) sc;
171 if (SyncopeFiqlParser.IEQ.equals(sfsc.getOperator())) {
172 ct = ConditionType.EQUALS;
173 } else if (SyncopeFiqlParser.NIEQ.equals(sfsc.getOperator())) {
174 ct = ConditionType.NOT_EQUALS;
175 }
176 }
177 switch (ct) {
178 case EQUALS:
179 if (SpecialAttr.RELATIONSHIP_TYPES.toString().equals(property)) {
180 clause.setComparator(SpecialAttr.NULL.toString().equals(value)
181 ? SearchClause.Comparator.EQUALS : SearchClause.Comparator.IS_NULL);
182 } else {
183 clause.setComparator(SpecialAttr.NULL.toString().equals(value)
184 ? SearchClause.Comparator.IS_NULL : SearchClause.Comparator.EQUALS);
185 }
186 break;
187
188 case NOT_EQUALS:
189 if (SpecialAttr.RELATIONSHIP_TYPES.toString().equals(property)) {
190 clause.setComparator(SpecialAttr.NULL.toString().equals(value)
191 ? SearchClause.Comparator.NOT_EQUALS : SearchClause.Comparator.IS_NOT_NULL);
192 } else {
193 clause.setComparator(SpecialAttr.NULL.toString().equals(value)
194 ? SearchClause.Comparator.IS_NOT_NULL : SearchClause.Comparator.NOT_EQUALS);
195 }
196 break;
197
198 case GREATER_OR_EQUALS:
199 clause.setComparator(SearchClause.Comparator.GREATER_OR_EQUALS);
200 break;
201
202 case GREATER_THAN:
203 clause.setComparator(SearchClause.Comparator.GREATER_THAN);
204 break;
205
206 case LESS_OR_EQUALS:
207 clause.setComparator(SearchClause.Comparator.LESS_OR_EQUALS);
208 break;
209
210 case LESS_THAN:
211 clause.setComparator(SearchClause.Comparator.LESS_THAN);
212 break;
213
214 default:
215 break;
216 }
217
218 return clause;
219 }
220
221 public static String buildFIQL(
222 final List<SearchClause> clauses,
223 final AbstractFiqlSearchConditionBuilder<?, ?, ?> builder) {
224
225 return buildFIQL(clauses, builder, Map.of(), NO_CUSTOM_CONDITION);
226 }
227
228 public static String buildFIQL(
229 final List<SearchClause> clauses,
230 final AbstractFiqlSearchConditionBuilder<?, ?, ?> builder,
231 final Map<String, PlainSchemaTO> availableSchemaTypes,
232 final Function<SearchClause, CompleteCondition> customCondition) {
233
234 LOG.debug("Generating FIQL from {}", clauses);
235
236 CompleteCondition prevCondition;
237 CompleteCondition condition = null;
238
239 boolean notTheFirst = false;
240
241 for (SearchClause clause : clauses) {
242 prevCondition = condition;
243
244 if (clause.getType() != null) {
245 String value = clause.getValue() == null
246 ? null
247 : ENCODINGS.keySet().stream().
248 reduce(clause.getValue(), (s, k) -> s.replace(k, ENCODINGS.get(k)));
249
250 switch (clause.getType()) {
251 case GROUP_MEMBER:
252 if (builder instanceof GroupFiqlSearchConditionBuilder) {
253 switch (clause.getComparator()) {
254 case EQUALS:
255 condition = ((GroupFiqlSearchConditionBuilder) builder).withMembers(value);
256 break;
257
258 case NOT_EQUALS:
259 condition = ((GroupFiqlSearchConditionBuilder) builder).withoutMembers(value);
260 break;
261
262 default:
263 }
264 }
265 break;
266
267 case GROUP_MEMBERSHIP:
268 if (StringUtils.isNotBlank(clause.getProperty())) {
269 String groupKey = clause.getProperty();
270
271 if (builder instanceof UserFiqlSearchConditionBuilder) {
272 condition = clause.getComparator() == SearchClause.Comparator.EQUALS
273 ? ((UserFiqlSearchConditionBuilder) builder).inGroups(groupKey)
274 : ((UserFiqlSearchConditionBuilder) builder).notInGroups(groupKey);
275 } else {
276 condition = clause.getComparator() == SearchClause.Comparator.EQUALS
277 ? ((AnyObjectFiqlSearchConditionBuilder) builder).inGroups(groupKey)
278 : ((AnyObjectFiqlSearchConditionBuilder) builder).notInGroups(groupKey);
279 }
280 }
281 break;
282
283 case AUX_CLASS:
284 if (StringUtils.isNotBlank(clause.getProperty())) {
285 condition = clause.getComparator() == SearchClause.Comparator.EQUALS
286 ? builder.hasAuxClasses(clause.getProperty())
287 : builder.hasNotAuxClasses(clause.getProperty());
288 }
289 break;
290
291 case RESOURCE:
292 if (StringUtils.isNotBlank(clause.getProperty())) {
293 condition = clause.getComparator() == SearchClause.Comparator.EQUALS
294 ? builder.hasResources(clause.getProperty())
295 : builder.hasNotResources(clause.getProperty());
296 }
297 break;
298
299 case ATTRIBUTE:
300 if (StringUtils.isNotBlank(clause.getProperty())) {
301 boolean isLong = false;
302 boolean isDouble = false;
303 boolean isBoolean = false;
304 if (availableSchemaTypes.get(clause.getProperty()) != null) {
305 switch (availableSchemaTypes.get(clause.getProperty()).getType()) {
306 case Long:
307 isLong = true;
308 break;
309
310 case Double:
311 isDouble = true;
312 break;
313
314 case Boolean:
315 isBoolean = true;
316 break;
317
318 default:
319 }
320 }
321
322 SyncopeProperty<?> property = builder.is(clause.getProperty());
323 switch (clause.getComparator()) {
324 case IS_NULL:
325 condition = builder.isNull(clause.getProperty());
326 break;
327
328 case IS_NOT_NULL:
329 condition = builder.isNotNull(clause.getProperty());
330 break;
331
332 case LESS_THAN:
333 condition = isLong
334 ? property.lessThan(NumberUtils.toLong(value))
335 : isDouble
336 ? property.lessThan(NumberUtils.toDouble(value))
337 : property.lexicalBefore(value);
338 break;
339
340 case LESS_OR_EQUALS:
341 condition = isLong
342 ? property.lessOrEqualTo(NumberUtils.toLong(value))
343 : isDouble
344 ? property.lessOrEqualTo(NumberUtils.toDouble(value))
345 : property.lexicalNotAfter(value);
346 break;
347
348 case GREATER_THAN:
349 condition = isLong
350 ? property.greaterThan(NumberUtils.toLong(value))
351 : isDouble
352 ? property.greaterThan(NumberUtils.toDouble(value))
353 : property.lexicalAfter(value);
354 break;
355
356 case GREATER_OR_EQUALS:
357 condition = isLong
358 ? property.greaterOrEqualTo(NumberUtils.toLong(value))
359 : isDouble
360 ? property.greaterOrEqualTo(NumberUtils.toDouble(value))
361 : property.lexicalNotBefore(value);
362 break;
363
364 case NOT_EQUALS:
365 condition = isLong
366 ? property.notEqualTo(NumberUtils.toLong(value))
367 : isDouble
368 ? property.notEqualTo(NumberUtils.toDouble(value))
369 : isBoolean
370 ? property.notEqualTo(BooleanUtils.toStringTrueFalse(
371 BooleanUtils.toBoolean(value)))
372 : property.notEqualTolIgnoreCase(value);
373 break;
374
375 case EQUALS:
376 condition = isLong
377 ? property.equalTo(NumberUtils.toLong(value))
378 : isDouble
379 ? property.equalTo(NumberUtils.toDouble(value))
380 : isBoolean
381 ? property.equalTo(BooleanUtils.toStringTrueFalse(
382 BooleanUtils.toBoolean(value)))
383 : property.equalToIgnoreCase(value);
384 break;
385
386 default:
387 condition = property.equalToIgnoreCase(value);
388 break;
389 }
390 }
391 break;
392
393 case ROLE_MEMBERSHIP:
394 if (StringUtils.isNotBlank(clause.getProperty())
395 && builder instanceof UserFiqlSearchConditionBuilder) {
396
397 switch (clause.getComparator()) {
398 case EQUALS:
399 condition = ((UserFiqlSearchConditionBuilder) builder).
400 inRoles(clause.getProperty());
401 break;
402 case NOT_EQUALS:
403 condition = ((UserFiqlSearchConditionBuilder) builder).
404 notInRoles(clause.getProperty());
405 break;
406 default:
407 break;
408 }
409 }
410 break;
411
412 case PRIVILEGE:
413 if (StringUtils.isNotBlank(clause.getProperty())
414 && builder instanceof UserFiqlSearchConditionBuilder) {
415
416 switch (clause.getComparator()) {
417 case EQUALS:
418 condition = ((UserFiqlSearchConditionBuilder) builder).
419 withPrivileges(clause.getProperty());
420 break;
421 case NOT_EQUALS:
422 condition = ((UserFiqlSearchConditionBuilder) builder).
423 withoutPrivileges(clause.getProperty());
424 break;
425 default:
426 break;
427 }
428 }
429 break;
430
431 case RELATIONSHIP:
432 if (StringUtils.isNotBlank(clause.getProperty())) {
433 if (builder instanceof UserFiqlSearchConditionBuilder) {
434 switch (clause.getComparator()) {
435 case IS_NOT_NULL:
436 condition = ((UserFiqlSearchConditionBuilder) builder).
437 inRelationshipTypes(clause.getProperty());
438 break;
439 case IS_NULL:
440 condition = ((UserFiqlSearchConditionBuilder) builder).
441 notInRelationshipTypes(clause.getProperty());
442 break;
443 case EQUALS:
444 condition = ((UserFiqlSearchConditionBuilder) builder).
445 inRelationships(value);
446 break;
447 case NOT_EQUALS:
448 condition = ((UserFiqlSearchConditionBuilder) builder).
449 notInRelationships(value);
450 break;
451 default:
452 break;
453 }
454 } else {
455 switch (clause.getComparator()) {
456 case IS_NOT_NULL:
457 condition = ((AnyObjectFiqlSearchConditionBuilder) builder).
458 inRelationshipTypes(clause.getProperty());
459 break;
460 case IS_NULL:
461 condition = ((AnyObjectFiqlSearchConditionBuilder) builder).
462 notInRelationshipTypes(clause.getProperty());
463 break;
464 case EQUALS:
465 condition = ((AnyObjectFiqlSearchConditionBuilder) builder).
466 inRelationships(value);
467 break;
468 case NOT_EQUALS:
469 condition = ((AnyObjectFiqlSearchConditionBuilder) builder).
470 notInRelationships(value);
471 break;
472 default:
473 break;
474 }
475 }
476 }
477 break;
478
479 case CUSTOM:
480 condition = customCondition.apply(clause);
481 break;
482
483 default:
484 break;
485 }
486 }
487
488 if (notTheFirst) {
489 if (clause.getOperator() == SearchClause.Operator.AND) {
490 condition = builder.and(condition, prevCondition);
491 }
492 if (clause.getOperator() == SearchClause.Operator.OR) {
493 condition = builder.or(condition, prevCondition);
494 }
495 }
496
497 notTheFirst = true;
498 }
499
500 String fiql = condition == null ? null : condition.query();
501 LOG.debug("Generated FIQL: {}", fiql);
502
503 return fiql;
504 }
505
506 private SearchUtils() {
507
508 }
509 }