| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.syncope.client.console.panels.search; |
| |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.function.Function; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| import org.apache.commons.collections4.BidiMap; |
| import org.apache.commons.collections4.bidimap.DualHashBidiMap; |
| import org.apache.commons.lang3.BooleanUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.commons.lang3.math.NumberUtils; |
| import org.apache.cxf.jaxrs.ext.search.ConditionType; |
| import org.apache.cxf.jaxrs.ext.search.SearchBean; |
| import org.apache.cxf.jaxrs.ext.search.SearchCondition; |
| import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition; |
| import org.apache.syncope.common.lib.search.AbstractFiqlSearchConditionBuilder; |
| import org.apache.syncope.common.lib.search.AnyObjectFiqlSearchConditionBuilder; |
| import org.apache.syncope.common.lib.search.GroupFiqlSearchConditionBuilder; |
| import org.apache.syncope.common.lib.search.SpecialAttr; |
| import org.apache.syncope.common.lib.search.SyncopeFiqlParser; |
| import org.apache.syncope.common.lib.search.SyncopeFiqlSearchCondition; |
| import org.apache.syncope.common.lib.search.SyncopeProperty; |
| import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder; |
| import org.apache.syncope.common.lib.to.PlainSchemaTO; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public final class SearchUtils implements Serializable { |
| |
| private static final long serialVersionUID = 398381905376547084L; |
| |
| public static final Function<SearchClause, CompleteCondition> NO_CUSTOM_CONDITION = clause -> null; |
| |
| private static final Logger LOG = LoggerFactory.getLogger(SearchUtils.class); |
| |
| private static final BidiMap<String, String> ENCODINGS = new DualHashBidiMap<>() { |
| |
| private static final long serialVersionUID = 5636572627689425575L; |
| |
| { |
| put(",", "%252C"); |
| put(";", "%253B"); |
| put("+", "%252B"); |
| } |
| }; |
| |
| private static Pattern getTypeConditionPattern(final String type) { |
| return Pattern.compile(String.format(";\\$type==%s|\\$type==%s;", type, type)); |
| } |
| |
| public static Map<String, List<SearchClause>> getSearchClauses(final Map<String, String> fiql) { |
| return fiql.entrySet().stream().collect(Collectors.toMap( |
| Map.Entry::getKey, |
| e -> getSearchClauses(e.getValue().replaceAll(getTypeConditionPattern(e.getKey()).pattern(), "")))); |
| } |
| |
| public static List<SearchClause> getSearchClauses(final String fiql) { |
| List<SearchClause> clauses = new ArrayList<>(); |
| if (StringUtils.isNotBlank(fiql)) { |
| try { |
| SyncopeFiqlParser<SearchBean> fiqlParser = new SyncopeFiqlParser<>( |
| SearchBean.class, AbstractFiqlSearchConditionBuilder.CONTEXTUAL_PROPERTIES); |
| clauses.addAll(getSearchClauses(fiqlParser.parse(fiql))); |
| } catch (Exception e) { |
| LOG.error("Unparseable FIQL expression '{}'", fiql, e); |
| } |
| } |
| return clauses; |
| } |
| |
| private static List<SearchClause> getSearchClauses(final SearchCondition<SearchBean> sc) { |
| List<SearchClause> clauses = new ArrayList<>(); |
| |
| if (sc.getStatement() == null) { |
| clauses.addAll(getCompoundSearchClauses(sc)); |
| } else { |
| clauses.add(getPrimitiveSearchClause(sc)); |
| } |
| |
| return clauses; |
| } |
| |
| private static List<SearchClause> getCompoundSearchClauses(final SearchCondition<SearchBean> sc) { |
| List<SearchClause> clauses = new ArrayList<>(); |
| |
| sc.getSearchConditions().forEach(searchCondition -> { |
| if (searchCondition.getStatement() == null) { |
| clauses.addAll(getCompoundSearchClauses(searchCondition)); |
| } else { |
| SearchClause clause = getPrimitiveSearchClause(searchCondition); |
| if (sc.getConditionType() == ConditionType.AND) { |
| clause.setOperator(SearchClause.Operator.AND); |
| } |
| if (sc.getConditionType() == ConditionType.OR) { |
| clause.setOperator(SearchClause.Operator.OR); |
| } |
| clauses.add(clause); |
| } |
| }); |
| |
| return clauses; |
| } |
| |
| private static SearchClause getPrimitiveSearchClause(final SearchCondition<SearchBean> sc) { |
| SearchClause clause = new SearchClause(); |
| |
| String property = sc.getCondition().getKeySet().iterator().next(); |
| clause.setProperty(property); |
| |
| String value = ENCODINGS.values().stream(). |
| reduce(sc.getCondition().get(property), (s, v) -> s.replace(v, ENCODINGS.getKey(v))); |
| clause.setValue(value); |
| |
| LOG.debug("Condition: " + sc.getCondition()); |
| |
| if (SpecialAttr.ROLES.toString().equals(property)) { |
| clause.setType(SearchClause.Type.ROLE_MEMBERSHIP); |
| clause.setProperty(value); |
| } else if (SpecialAttr.PRIVILEGES.toString().equals(property)) { |
| clause.setType(SearchClause.Type.PRIVILEGE); |
| clause.setProperty(value); |
| } else if (SpecialAttr.RELATIONSHIPS.toString().equals(property)) { |
| clause.setType(SearchClause.Type.RELATIONSHIP); |
| clause.setProperty(value); |
| } else if (SpecialAttr.RELATIONSHIP_TYPES.toString().equals(property)) { |
| clause.setType(SearchClause.Type.RELATIONSHIP); |
| clause.setProperty(value); |
| } else if (SpecialAttr.GROUPS.toString().equals(property)) { |
| clause.setType(SearchClause.Type.GROUP_MEMBERSHIP); |
| clause.setProperty(value); |
| } else if (SpecialAttr.RESOURCES.toString().equals(property)) { |
| clause.setType(SearchClause.Type.RESOURCE); |
| clause.setProperty(value); |
| } else if (SpecialAttr.MEMBER.toString().equals(property)) { |
| clause.setType(SearchClause.Type.GROUP_MEMBER); |
| clause.setProperty(value); |
| } else if (property.startsWith("$")) { |
| clause.setType(SearchClause.Type.CUSTOM); |
| clause.setProperty(value); |
| } else { |
| clause.setType(SearchClause.Type.ATTRIBUTE); |
| } |
| |
| ConditionType ct = sc.getConditionType(); |
| if (sc instanceof SyncopeFiqlSearchCondition && sc.getConditionType() == ConditionType.CUSTOM) { |
| SyncopeFiqlSearchCondition<SearchBean> sfsc = (SyncopeFiqlSearchCondition<SearchBean>) sc; |
| if (SyncopeFiqlParser.IEQ.equals(sfsc.getOperator())) { |
| ct = ConditionType.EQUALS; |
| } else if (SyncopeFiqlParser.NIEQ.equals(sfsc.getOperator())) { |
| ct = ConditionType.NOT_EQUALS; |
| } |
| } |
| switch (ct) { |
| case EQUALS: |
| if (SpecialAttr.RELATIONSHIP_TYPES.toString().equals(property)) { |
| clause.setComparator(SpecialAttr.NULL.toString().equals(value) |
| ? SearchClause.Comparator.EQUALS : SearchClause.Comparator.IS_NULL); |
| } else { |
| clause.setComparator(SpecialAttr.NULL.toString().equals(value) |
| ? SearchClause.Comparator.IS_NULL : SearchClause.Comparator.EQUALS); |
| } |
| break; |
| |
| case NOT_EQUALS: |
| if (SpecialAttr.RELATIONSHIP_TYPES.toString().equals(property)) { |
| clause.setComparator(SpecialAttr.NULL.toString().equals(value) |
| ? SearchClause.Comparator.NOT_EQUALS : SearchClause.Comparator.IS_NOT_NULL); |
| } else { |
| clause.setComparator(SpecialAttr.NULL.toString().equals(value) |
| ? SearchClause.Comparator.IS_NOT_NULL : SearchClause.Comparator.NOT_EQUALS); |
| } |
| break; |
| |
| case GREATER_OR_EQUALS: |
| clause.setComparator(SearchClause.Comparator.GREATER_OR_EQUALS); |
| break; |
| |
| case GREATER_THAN: |
| clause.setComparator(SearchClause.Comparator.GREATER_THAN); |
| break; |
| |
| case LESS_OR_EQUALS: |
| clause.setComparator(SearchClause.Comparator.LESS_OR_EQUALS); |
| break; |
| |
| case LESS_THAN: |
| clause.setComparator(SearchClause.Comparator.LESS_THAN); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return clause; |
| } |
| |
| public static String buildFIQL( |
| final List<SearchClause> clauses, |
| final AbstractFiqlSearchConditionBuilder<?, ?, ?> builder) { |
| |
| return buildFIQL(clauses, builder, Map.of(), NO_CUSTOM_CONDITION); |
| } |
| |
| public static String buildFIQL( |
| final List<SearchClause> clauses, |
| final AbstractFiqlSearchConditionBuilder<?, ?, ?> builder, |
| final Map<String, PlainSchemaTO> availableSchemaTypes, |
| final Function<SearchClause, CompleteCondition> customCondition) { |
| |
| LOG.debug("Generating FIQL from {}", clauses); |
| |
| CompleteCondition prevCondition; |
| CompleteCondition condition = null; |
| |
| boolean notTheFirst = false; |
| |
| for (SearchClause clause : clauses) { |
| prevCondition = condition; |
| |
| if (clause.getType() != null) { |
| String value = clause.getValue() == null |
| ? null |
| : ENCODINGS.keySet().stream(). |
| reduce(clause.getValue(), (s, k) -> s.replace(k, ENCODINGS.get(k))); |
| |
| switch (clause.getType()) { |
| case GROUP_MEMBER: |
| if (builder instanceof GroupFiqlSearchConditionBuilder) { |
| switch (clause.getComparator()) { |
| case EQUALS: |
| condition = ((GroupFiqlSearchConditionBuilder) builder).withMembers(value); |
| break; |
| |
| case NOT_EQUALS: |
| condition = ((GroupFiqlSearchConditionBuilder) builder).withoutMembers(value); |
| break; |
| |
| default: |
| } |
| } |
| break; |
| |
| case GROUP_MEMBERSHIP: |
| if (StringUtils.isNotBlank(clause.getProperty())) { |
| String groupKey = clause.getProperty(); |
| |
| if (builder instanceof UserFiqlSearchConditionBuilder) { |
| condition = clause.getComparator() == SearchClause.Comparator.EQUALS |
| ? ((UserFiqlSearchConditionBuilder) builder).inGroups(groupKey) |
| : ((UserFiqlSearchConditionBuilder) builder).notInGroups(groupKey); |
| } else { |
| condition = clause.getComparator() == SearchClause.Comparator.EQUALS |
| ? ((AnyObjectFiqlSearchConditionBuilder) builder).inGroups(groupKey) |
| : ((AnyObjectFiqlSearchConditionBuilder) builder).notInGroups(groupKey); |
| } |
| } |
| break; |
| |
| case RESOURCE: |
| if (StringUtils.isNotBlank(clause.getProperty())) { |
| condition = clause.getComparator() == SearchClause.Comparator.EQUALS |
| ? builder.hasResources(clause.getProperty()) |
| : builder.hasNotResources(clause.getProperty()); |
| } |
| break; |
| |
| case ATTRIBUTE: |
| if (StringUtils.isNotBlank(clause.getProperty())) { |
| boolean isLong = false; |
| boolean isDouble = false; |
| boolean isBoolean = false; |
| if (availableSchemaTypes.get(clause.getProperty()) != null) { |
| switch (availableSchemaTypes.get(clause.getProperty()).getType()) { |
| case Long: |
| isLong = true; |
| break; |
| |
| case Double: |
| isDouble = true; |
| break; |
| |
| case Boolean: |
| isBoolean = true; |
| break; |
| |
| default: |
| } |
| } |
| |
| SyncopeProperty<?> property = builder.is(clause.getProperty()); |
| switch (clause.getComparator()) { |
| case IS_NULL: |
| condition = builder.isNull(clause.getProperty()); |
| break; |
| |
| case IS_NOT_NULL: |
| condition = builder.isNotNull(clause.getProperty()); |
| break; |
| |
| case LESS_THAN: |
| condition = isLong |
| ? property.lessThan(NumberUtils.toLong(value)) |
| : isDouble |
| ? property.lessThan(NumberUtils.toDouble(value)) |
| : property.lexicalBefore(value); |
| break; |
| |
| case LESS_OR_EQUALS: |
| condition = isLong |
| ? property.lessOrEqualTo(NumberUtils.toLong(value)) |
| : isDouble |
| ? property.lessOrEqualTo(NumberUtils.toDouble(value)) |
| : property.lexicalNotAfter(value); |
| break; |
| |
| case GREATER_THAN: |
| condition = isLong |
| ? property.greaterThan(NumberUtils.toLong(value)) |
| : isDouble |
| ? property.greaterThan(NumberUtils.toDouble(value)) |
| : property.lexicalAfter(value); |
| break; |
| |
| case GREATER_OR_EQUALS: |
| condition = isLong |
| ? property.greaterOrEqualTo(NumberUtils.toLong(value)) |
| : isDouble |
| ? property.greaterOrEqualTo(NumberUtils.toDouble(value)) |
| : property.lexicalNotBefore(value); |
| break; |
| |
| case NOT_EQUALS: |
| condition = isLong |
| ? property.notEqualTo(NumberUtils.toLong(value)) |
| : isDouble |
| ? property.notEqualTo(NumberUtils.toDouble(value)) |
| : isBoolean |
| ? property.notEqualTo(BooleanUtils.toStringTrueFalse( |
| BooleanUtils.toBoolean(value))) |
| : property.notEqualTolIgnoreCase(value); |
| break; |
| |
| case EQUALS: |
| condition = isLong |
| ? property.equalTo(NumberUtils.toLong(value)) |
| : isDouble |
| ? property.equalTo(NumberUtils.toDouble(value)) |
| : isBoolean |
| ? property.equalTo(BooleanUtils.toStringTrueFalse( |
| BooleanUtils.toBoolean(value))) |
| : property.equalToIgnoreCase(value); |
| break; |
| |
| default: |
| condition = property.equalToIgnoreCase(value); |
| break; |
| } |
| } |
| break; |
| |
| case ROLE_MEMBERSHIP: |
| if (StringUtils.isNotBlank(clause.getProperty()) |
| && builder instanceof UserFiqlSearchConditionBuilder) { |
| |
| switch (clause.getComparator()) { |
| case EQUALS: |
| condition = ((UserFiqlSearchConditionBuilder) builder). |
| inRoles(clause.getProperty()); |
| break; |
| case NOT_EQUALS: |
| condition = ((UserFiqlSearchConditionBuilder) builder). |
| notInRoles(clause.getProperty()); |
| break; |
| default: |
| break; |
| } |
| } |
| break; |
| |
| case PRIVILEGE: |
| if (StringUtils.isNotBlank(clause.getProperty()) |
| && builder instanceof UserFiqlSearchConditionBuilder) { |
| |
| switch (clause.getComparator()) { |
| case EQUALS: |
| condition = ((UserFiqlSearchConditionBuilder) builder). |
| withPrivileges(clause.getProperty()); |
| break; |
| case NOT_EQUALS: |
| condition = ((UserFiqlSearchConditionBuilder) builder). |
| withoutPrivileges(clause.getProperty()); |
| break; |
| default: |
| break; |
| } |
| } |
| break; |
| |
| case RELATIONSHIP: |
| if (StringUtils.isNotBlank(clause.getProperty())) { |
| if (builder instanceof UserFiqlSearchConditionBuilder) { |
| switch (clause.getComparator()) { |
| case IS_NOT_NULL: |
| condition = ((UserFiqlSearchConditionBuilder) builder). |
| inRelationshipTypes(clause.getProperty()); |
| break; |
| case IS_NULL: |
| condition = ((UserFiqlSearchConditionBuilder) builder). |
| notInRelationshipTypes(clause.getProperty()); |
| break; |
| case EQUALS: |
| condition = ((UserFiqlSearchConditionBuilder) builder). |
| inRelationships(value); |
| break; |
| case NOT_EQUALS: |
| condition = ((UserFiqlSearchConditionBuilder) builder). |
| notInRelationships(value); |
| break; |
| default: |
| break; |
| } |
| } else { |
| switch (clause.getComparator()) { |
| case IS_NOT_NULL: |
| condition = ((AnyObjectFiqlSearchConditionBuilder) builder). |
| inRelationshipTypes(clause.getProperty()); |
| break; |
| case IS_NULL: |
| condition = ((AnyObjectFiqlSearchConditionBuilder) builder). |
| notInRelationshipTypes(clause.getProperty()); |
| break; |
| case EQUALS: |
| condition = ((AnyObjectFiqlSearchConditionBuilder) builder). |
| inRelationships(value); |
| break; |
| case NOT_EQUALS: |
| condition = ((AnyObjectFiqlSearchConditionBuilder) builder). |
| notInRelationships(value); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| break; |
| |
| case CUSTOM: |
| condition = customCondition.apply(clause); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| if (notTheFirst) { |
| if (clause.getOperator() == SearchClause.Operator.AND) { |
| condition = builder.and(prevCondition, condition); |
| } |
| if (clause.getOperator() == SearchClause.Operator.OR) { |
| condition = builder.or(prevCondition, condition); |
| } |
| } |
| |
| notTheFirst = true; |
| } |
| |
| String fiql = condition == null ? null : condition.query(); |
| LOG.debug("Generated FIQL: {}", fiql); |
| |
| return fiql; |
| } |
| |
| private SearchUtils() { |
| // private constructor for static utility class |
| } |
| } |