blob: 38b5d5533313bad1a91d1cd4cc558762c55afc0c [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.core.persistence.jpa.dao;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.ValidationException;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.AttrSchemaType;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.search.AbstractSearchCond;
import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
import org.apache.syncope.core.persistence.api.dao.search.AssignableCond;
import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
import org.apache.syncope.core.persistence.api.dao.search.RelationshipCond;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.AnyUtils;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.Entity;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
import org.apache.syncope.core.persistence.api.entity.PlainSchema;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.jpa.entity.JPAPlainSchema;
public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implements AnySearchDAO {
private static final String[] ORDER_BY_NOT_ALLOWED = {
"serialVersionUID", "password", "securityQuestion", "securityAnswer", "token", "tokenExpireTime"
};
protected static final String[] RELATIONSHIP_FIELDS = new String[] { "realm", "userOwner", "groupOwner" };
protected static SearchCond buildEffectiveCond(
final SearchCond cond,
final Set<String> dynRealmKeys,
final Set<String> groupOwners,
final AnyTypeKind kind) {
List<SearchCond> result = new ArrayList<>();
result.add(cond);
List<SearchCond> dynRealmConds = dynRealmKeys.stream().map(key -> {
DynRealmCond dynRealmCond = new DynRealmCond();
dynRealmCond.setDynRealm(key);
return SearchCond.getLeaf(dynRealmCond);
}).collect(Collectors.toList());
if (!dynRealmConds.isEmpty()) {
result.add(SearchCond.getOr(dynRealmConds));
}
List<SearchCond> groupOwnerConds = groupOwners.stream().map(key -> {
AbstractSearchCond asc;
if (kind == AnyTypeKind.GROUP) {
AnyCond anyCond = new AnyCond(AttrCond.Type.EQ);
anyCond.setSchema("id");
anyCond.setExpression(key);
asc = anyCond;
} else {
MembershipCond membershipCond = new MembershipCond();
membershipCond.setGroup(key);
asc = membershipCond;
}
return SearchCond.getLeaf(asc);
}).collect(Collectors.toList());
if (!groupOwnerConds.isEmpty()) {
result.add(SearchCond.getOr(groupOwnerConds));
}
return SearchCond.getAnd(result);
}
protected final RealmDAO realmDAO;
protected final DynRealmDAO dynRealmDAO;
protected final UserDAO userDAO;
protected final GroupDAO groupDAO;
protected final AnyObjectDAO anyObjectDAO;
protected final PlainSchemaDAO plainSchemaDAO;
protected final EntityFactory entityFactory;
protected final AnyUtilsFactory anyUtilsFactory;
public AbstractAnySearchDAO(
final RealmDAO realmDAO,
final DynRealmDAO dynRealmDAO,
final UserDAO userDAO,
final GroupDAO groupDAO,
final AnyObjectDAO anyObjectDAO,
final PlainSchemaDAO plainSchemaDAO,
final EntityFactory entityFactory,
final AnyUtilsFactory anyUtilsFactory) {
this.realmDAO = realmDAO;
this.dynRealmDAO = dynRealmDAO;
this.userDAO = userDAO;
this.groupDAO = groupDAO;
this.anyObjectDAO = anyObjectDAO;
this.plainSchemaDAO = plainSchemaDAO;
this.entityFactory = entityFactory;
this.anyUtilsFactory = anyUtilsFactory;
}
protected abstract int doCount(Set<String> adminRealms, SearchCond cond, AnyTypeKind kind);
@Override
public int count(final Set<String> adminRealms, final SearchCond cond, final AnyTypeKind kind) {
if (adminRealms == null || adminRealms.isEmpty()) {
LOG.error("No realms provided");
return 0;
}
LOG.debug("Search condition:\n{}", cond);
if (cond == null || !cond.isValid()) {
LOG.error("Invalid search condition:\n{}", cond);
return 0;
}
return doCount(adminRealms, cond, kind);
}
@Override
public <T extends Any<?>> List<T> search(final SearchCond cond, final AnyTypeKind kind) {
return search(cond, List.of(), kind);
}
@Override
public <T extends Any<?>> List<T> search(
final SearchCond cond, final List<OrderByClause> orderBy, final AnyTypeKind kind) {
return search(SyncopeConstants.FULL_ADMIN_REALMS, cond, -1, -1, orderBy, kind);
}
protected abstract <T extends Any<?>> List<T> doSearch(
Set<String> adminRealms,
SearchCond searchCondition,
int page,
int itemsPerPage,
List<OrderByClause> orderBy,
AnyTypeKind kind);
protected Pair<PlainSchema, PlainAttrValue> check(final AttrCond cond, final AnyTypeKind kind) {
AnyUtils anyUtils = anyUtilsFactory.getInstance(kind);
PlainSchema schema = plainSchemaDAO.find(cond.getSchema());
if (schema == null) {
LOG.warn("Ignoring invalid schema '{}'", cond.getSchema());
throw new IllegalArgumentException();
}
PlainAttrValue attrValue = schema.isUniqueConstraint()
? anyUtils.newPlainAttrUniqueValue()
: anyUtils.newPlainAttrValue();
try {
if (cond.getType() != AttrCond.Type.LIKE
&& cond.getType() != AttrCond.Type.ILIKE
&& cond.getType() != AttrCond.Type.ISNULL
&& cond.getType() != AttrCond.Type.ISNOTNULL) {
((JPAPlainSchema) schema).validator().validate(cond.getExpression(), attrValue);
}
} catch (ValidationException e) {
LOG.error("Could not validate expression '" + cond.getExpression() + '\'', e);
throw new IllegalArgumentException();
}
return Pair.of(schema, attrValue);
}
protected Triple<PlainSchema, PlainAttrValue, AnyCond> check(final AnyCond cond, final AnyTypeKind kind) {
AnyCond computed = new AnyCond(cond.getType());
computed.setSchema(cond.getSchema());
computed.setExpression(cond.getExpression());
AnyUtils anyUtils = anyUtilsFactory.getInstance(kind);
Field anyField = anyUtils.getField(computed.getSchema());
if (anyField == null) {
LOG.warn("Ignoring invalid field '{}'", computed.getSchema());
throw new IllegalArgumentException();
}
// Keeps track of difference between entity's getKey() and JPA @Id fields
if ("key".equals(computed.getSchema())) {
computed.setSchema("id");
}
PlainSchema schema = entityFactory.newEntity(PlainSchema.class);
schema.setKey(anyField.getName());
for (AttrSchemaType attrSchemaType : AttrSchemaType.values()) {
if (anyField.getType().isAssignableFrom(attrSchemaType.getType())) {
schema.setType(attrSchemaType);
}
}
// Deal with any Integer fields logically mapping to boolean values
boolean foundBooleanMin = false;
boolean foundBooleanMax = false;
if (Integer.class.equals(anyField.getType())) {
for (Annotation annotation : anyField.getAnnotations()) {
if (Min.class.equals(annotation.annotationType())) {
foundBooleanMin = ((Min) annotation).value() == 0;
} else if (Max.class.equals(annotation.annotationType())) {
foundBooleanMax = ((Max) annotation).value() == 1;
}
}
}
if (foundBooleanMin && foundBooleanMax) {
schema.setType(AttrSchemaType.Boolean);
}
// Deal with any fields representing relationships to other entities
if (ArrayUtils.contains(RELATIONSHIP_FIELDS, computed.getSchema())) {
computed.setSchema(computed.getSchema() + "_id");
schema.setType(AttrSchemaType.String);
}
PlainAttrValue attrValue = anyUtils.newPlainAttrValue();
if (computed.getType() != AttrCond.Type.LIKE
&& computed.getType() != AttrCond.Type.ILIKE
&& computed.getType() != AttrCond.Type.ISNULL
&& computed.getType() != AttrCond.Type.ISNOTNULL) {
try {
((JPAPlainSchema) schema).validator().validate(computed.getExpression(), attrValue);
} catch (ValidationException e) {
LOG.error("Could not validate expression '" + computed.getExpression() + '\'', e);
throw new IllegalArgumentException();
}
}
return Triple.of(schema, attrValue, computed);
}
protected List<String> check(final MembershipCond cond) {
if (SyncopeConstants.UUID_PATTERN.matcher(cond.getGroup()).matches()) {
return List.of(cond.getGroup());
}
List<String> matching = cond.getGroup().indexOf('%') == -1
? Optional.ofNullable(groupDAO.findKey(cond.getGroup())).map(List::of).orElseGet(List::of)
: groupDAO.findKeysByNamePattern(cond.getGroup());
if (matching.isEmpty()) {
LOG.error("Could not find group(s) for '{}'", cond.getGroup());
throw new IllegalArgumentException();
}
return matching;
}
protected String check(final RelationshipCond cond) {
String rightAnyObjectKey;
if (SyncopeConstants.UUID_PATTERN.matcher(cond.getAnyObject()).matches()) {
rightAnyObjectKey = cond.getAnyObject();
} else {
AnyObject anyObject = anyObjectDAO.findByName(cond.getAnyObject());
rightAnyObjectKey = Optional.ofNullable(anyObject).map(Entity::getKey).orElse(null);
}
if (rightAnyObjectKey == null) {
LOG.error("Could not find any object for '" + cond.getAnyObject() + '\'');
throw new IllegalArgumentException();
}
return rightAnyObjectKey;
}
protected Realm check(final AssignableCond cond) {
Realm realm = realmDAO.findByFullPath(cond.getRealmFullPath());
if (realm == null) {
LOG.error("Could not find realm for '" + cond.getRealmFullPath() + '\'');
throw new IllegalArgumentException();
}
return realm;
}
protected String check(final MemberCond cond) {
String memberKey;
if (SyncopeConstants.UUID_PATTERN.matcher(cond.getMember()).matches()) {
memberKey = cond.getMember();
} else {
Any<?> member = userDAO.findByUsername(cond.getMember());
if (member == null) {
member = anyObjectDAO.findByName(cond.getMember());
}
memberKey = Optional.ofNullable(member).map(Entity::getKey).orElse(null);
}
if (memberKey == null) {
LOG.error("Could not find user or any object for '" + cond.getMember() + '\'');
throw new IllegalArgumentException();
}
return memberKey;
}
@SuppressWarnings("unchecked")
protected <T extends Any<?>> List<T> buildResult(final List<Object> raw, final AnyTypeKind kind) {
List<String> keys = raw.stream().
map(key -> key instanceof Object[] ? (String) ((Object[]) key)[0] : ((String) key)).
collect(Collectors.toList());
// sort anys according to keys' sorting, as their ordering is same as raw, e.g. the actual sql query results
List<Any<?>> anys = anyUtilsFactory.getInstance(kind).dao().findByKeys(keys).stream().
sorted(Comparator.comparing(any -> keys.indexOf(any.getKey()))).collect(Collectors.toList());
keys.stream().filter(key -> !anys.stream().anyMatch(any -> key.equals(any.getKey()))).
forEach(key -> LOG.error("Could not find {} with id {}, even if returned by native query", kind, key));
return (List<T>) anys;
}
@Override
public <T extends Any<?>> List<T> search(
final Set<String> adminRealms,
final SearchCond cond,
final int page,
final int itemsPerPage,
final List<OrderByClause> orderBy,
final AnyTypeKind kind) {
if (adminRealms == null || adminRealms.isEmpty()) {
LOG.error("No realms provided");
return List.of();
}
LOG.debug("Search condition:\n{}", cond);
if (cond == null || !cond.isValid()) {
LOG.error("Invalid search condition:\n{}", cond);
return List.of();
}
List<OrderByClause> effectiveOrderBy;
if (orderBy.isEmpty()) {
OrderByClause keyClause = new OrderByClause();
keyClause.setField(kind == AnyTypeKind.USER ? "username" : "name");
keyClause.setDirection(OrderByClause.Direction.ASC);
effectiveOrderBy = List.of(keyClause);
} else {
effectiveOrderBy = orderBy.stream().
filter(clause -> !ArrayUtils.contains(ORDER_BY_NOT_ALLOWED, clause.getField())).
collect(Collectors.toList());
}
return doSearch(adminRealms, cond, page, itemsPerPage, effectiveOrderBy, kind);
}
}