blob: 57e8a60389f727c9dd14fd5b3621b261d1054c4c [file] [log] [blame]
/*
* 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
}
}