View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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         // private constructor for static utility class
508     }
509 }