1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.persistence.jpa.dao;
20
21 import java.lang.annotation.Annotation;
22 import java.lang.reflect.Field;
23 import java.util.ArrayList;
24 import java.util.Comparator;
25 import java.util.List;
26 import java.util.Optional;
27 import java.util.Set;
28 import java.util.stream.Collectors;
29 import javax.validation.ValidationException;
30 import javax.validation.constraints.Max;
31 import javax.validation.constraints.Min;
32 import org.apache.commons.lang3.ArrayUtils;
33 import org.apache.commons.lang3.tuple.Pair;
34 import org.apache.commons.lang3.tuple.Triple;
35 import org.apache.syncope.common.lib.SyncopeConstants;
36 import org.apache.syncope.common.lib.types.AnyTypeKind;
37 import org.apache.syncope.common.lib.types.AttrSchemaType;
38 import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
39 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
40 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
41 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
42 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
43 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
44 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
45 import org.apache.syncope.core.persistence.api.dao.UserDAO;
46 import org.apache.syncope.core.persistence.api.dao.search.AbstractSearchCond;
47 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
48 import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
49 import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
50 import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
51 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
52 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
53 import org.apache.syncope.core.persistence.api.dao.search.RelationshipCond;
54 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
55 import org.apache.syncope.core.persistence.api.entity.Any;
56 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
57 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
58 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
59 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
60 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
61 import org.apache.syncope.core.persistence.api.entity.Realm;
62 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
63 import org.springframework.util.CollectionUtils;
64
65 public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implements AnySearchDAO {
66
67 private static final String[] ORDER_BY_NOT_ALLOWED = {
68 "serialVersionUID", "password", "securityQuestion", "securityAnswer", "token", "tokenExpireTime"
69 };
70
71 protected static final String[] RELATIONSHIP_FIELDS = new String[] { "realm", "userOwner", "groupOwner" };
72
73 protected static SearchCond buildEffectiveCond(
74 final SearchCond cond,
75 final Set<String> dynRealmKeys,
76 final Set<String> groupOwners,
77 final AnyTypeKind kind) {
78
79 List<SearchCond> result = new ArrayList<>();
80 result.add(cond);
81
82 List<SearchCond> dynRealmConds = dynRealmKeys.stream().map(key -> {
83 DynRealmCond dynRealmCond = new DynRealmCond();
84 dynRealmCond.setDynRealm(key);
85 return SearchCond.getLeaf(dynRealmCond);
86 }).collect(Collectors.toList());
87 if (!dynRealmConds.isEmpty()) {
88 result.add(SearchCond.getOr(dynRealmConds));
89 }
90
91 List<SearchCond> groupOwnerConds = groupOwners.stream().map(key -> {
92 AbstractSearchCond asc;
93 if (kind == AnyTypeKind.GROUP) {
94 AnyCond anyCond = new AnyCond(AttrCond.Type.EQ);
95 anyCond.setSchema("id");
96 anyCond.setExpression(key);
97 asc = anyCond;
98 } else {
99 MembershipCond membershipCond = new MembershipCond();
100 membershipCond.setGroup(key);
101 asc = membershipCond;
102 }
103 return SearchCond.getLeaf(asc);
104 }).collect(Collectors.toList());
105 if (!groupOwnerConds.isEmpty()) {
106 result.add(SearchCond.getOr(groupOwnerConds));
107 }
108
109 return SearchCond.getAnd(result);
110 }
111
112 protected final RealmDAO realmDAO;
113
114 protected final DynRealmDAO dynRealmDAO;
115
116 protected final UserDAO userDAO;
117
118 protected final GroupDAO groupDAO;
119
120 protected final AnyObjectDAO anyObjectDAO;
121
122 protected final PlainSchemaDAO plainSchemaDAO;
123
124 protected final EntityFactory entityFactory;
125
126 protected final AnyUtilsFactory anyUtilsFactory;
127
128 protected final PlainAttrValidationManager validator;
129
130 public AbstractAnySearchDAO(
131 final RealmDAO realmDAO,
132 final DynRealmDAO dynRealmDAO,
133 final UserDAO userDAO,
134 final GroupDAO groupDAO,
135 final AnyObjectDAO anyObjectDAO,
136 final PlainSchemaDAO plainSchemaDAO,
137 final EntityFactory entityFactory,
138 final AnyUtilsFactory anyUtilsFactory,
139 final PlainAttrValidationManager validator) {
140
141 this.realmDAO = realmDAO;
142 this.dynRealmDAO = dynRealmDAO;
143 this.userDAO = userDAO;
144 this.groupDAO = groupDAO;
145 this.anyObjectDAO = anyObjectDAO;
146 this.plainSchemaDAO = plainSchemaDAO;
147 this.entityFactory = entityFactory;
148 this.anyUtilsFactory = anyUtilsFactory;
149 this.validator = validator;
150 }
151
152 protected abstract int doCount(
153 Realm base, boolean recursive, Set<String> adminRealms, SearchCond cond, AnyTypeKind kind);
154
155 @Override
156 public int count(
157 final Realm base,
158 final boolean recursive,
159 final Set<String> adminRealms,
160 final SearchCond cond,
161 final AnyTypeKind kind) {
162
163 if (CollectionUtils.isEmpty(adminRealms)) {
164 LOG.error("No realms provided");
165 return 0;
166 }
167
168 LOG.debug("Search condition:\n{}", cond);
169 if (cond == null || !cond.isValid()) {
170 LOG.error("Invalid search condition:\n{}", cond);
171 return 0;
172 }
173
174 return doCount(base, recursive, adminRealms, cond, kind);
175 }
176
177 @Override
178 public <T extends Any<?>> List<T> search(final SearchCond cond, final AnyTypeKind kind) {
179 return search(cond, List.of(), kind);
180 }
181
182 @Override
183 public <T extends Any<?>> List<T> search(
184 final SearchCond cond, final List<OrderByClause> orderBy, final AnyTypeKind kind) {
185
186 return search(realmDAO.getRoot(), true, SyncopeConstants.FULL_ADMIN_REALMS, cond, -1, -1, orderBy, kind);
187 }
188
189 protected abstract <T extends Any<?>> List<T> doSearch(
190 Realm base,
191 boolean recursive,
192 Set<String> adminRealms,
193 SearchCond searchCondition,
194 int page,
195 int itemsPerPage,
196 List<OrderByClause> orderBy,
197 AnyTypeKind kind);
198
199 protected Pair<PlainSchema, PlainAttrValue> check(final AttrCond cond, final AnyTypeKind kind) {
200 AnyUtils anyUtils = anyUtilsFactory.getInstance(kind);
201
202 PlainSchema schema = plainSchemaDAO.find(cond.getSchema());
203 if (schema == null) {
204 throw new IllegalArgumentException("Invalid schema " + cond.getSchema());
205 }
206
207 PlainAttrValue attrValue = schema.isUniqueConstraint()
208 ? anyUtils.newPlainAttrUniqueValue()
209 : anyUtils.newPlainAttrValue();
210 try {
211 if (cond.getType() != AttrCond.Type.LIKE
212 && cond.getType() != AttrCond.Type.ILIKE
213 && cond.getType() != AttrCond.Type.ISNULL
214 && cond.getType() != AttrCond.Type.ISNOTNULL) {
215
216 validator.validate(schema, cond.getExpression(), attrValue);
217 }
218 } catch (ValidationException e) {
219 throw new IllegalArgumentException("Could not validate expression " + cond.getExpression());
220 }
221
222 return Pair.of(schema, attrValue);
223 }
224
225 protected Triple<PlainSchema, PlainAttrValue, AnyCond> check(final AnyCond cond, final AnyTypeKind kind) {
226 AnyCond computed = new AnyCond(cond.getType());
227 computed.setSchema(cond.getSchema());
228 computed.setExpression(cond.getExpression());
229
230 AnyUtils anyUtils = anyUtilsFactory.getInstance(kind);
231
232 Field anyField = anyUtils.getField(computed.getSchema());
233 if (anyField == null) {
234 throw new IllegalArgumentException("Invalid schema " + computed.getSchema());
235 }
236
237 if ("key".equals(computed.getSchema())) {
238 computed.setSchema("id");
239 }
240
241 PlainSchema schema = entityFactory.newEntity(PlainSchema.class);
242 schema.setKey(anyField.getName());
243 for (AttrSchemaType attrSchemaType : AttrSchemaType.values()) {
244 if (anyField.getType().isAssignableFrom(attrSchemaType.getType())) {
245 schema.setType(attrSchemaType);
246 }
247 }
248
249
250 boolean foundBooleanMin = false;
251 boolean foundBooleanMax = false;
252 if (Integer.class.equals(anyField.getType())) {
253 for (Annotation annotation : anyField.getAnnotations()) {
254 if (Min.class.equals(annotation.annotationType())) {
255 foundBooleanMin = ((Min) annotation).value() == 0;
256 } else if (Max.class.equals(annotation.annotationType())) {
257 foundBooleanMax = ((Max) annotation).value() == 1;
258 }
259 }
260 }
261 if (foundBooleanMin && foundBooleanMax) {
262 schema.setType(AttrSchemaType.Boolean);
263 }
264
265
266 if (ArrayUtils.contains(RELATIONSHIP_FIELDS, computed.getSchema())) {
267 computed.setSchema(computed.getSchema() + "_id");
268 schema.setType(AttrSchemaType.String);
269 }
270
271 PlainAttrValue attrValue = anyUtils.newPlainAttrValue();
272 if (computed.getType() != AttrCond.Type.LIKE
273 && computed.getType() != AttrCond.Type.ILIKE
274 && computed.getType() != AttrCond.Type.ISNULL
275 && computed.getType() != AttrCond.Type.ISNOTNULL) {
276
277 try {
278 validator.validate(schema, computed.getExpression(), attrValue);
279 } catch (ValidationException e) {
280 throw new IllegalArgumentException("Could not validate expression " + computed.getExpression());
281 }
282 }
283
284 return Triple.of(schema, attrValue, computed);
285 }
286
287 protected List<String> check(final MembershipCond cond) {
288 List<String> groups = SyncopeConstants.UUID_PATTERN.matcher(cond.getGroup()).matches()
289 ? List.of(cond.getGroup())
290 : cond.getGroup().indexOf('%') == -1
291 ? Optional.ofNullable(groupDAO.findKey(cond.getGroup())).map(List::of).orElseGet(List::of)
292 : groupDAO.findKeysByNamePattern(cond.getGroup());
293
294 if (groups.isEmpty()) {
295 throw new IllegalArgumentException("Could not find group(s) for " + cond.getGroup());
296 }
297
298 return groups;
299 }
300
301 protected Set<String> check(final RelationshipCond cond) {
302 Set<String> rightAnyObjects = cond.getAnyObject() == null
303 ? Set.of()
304 : SyncopeConstants.UUID_PATTERN.matcher(cond.getAnyObject()).matches()
305 ? Set.of(cond.getAnyObject())
306 : anyObjectDAO.findByName(cond.getAnyObject()).stream().
307 map(AnyObject::getKey).collect(Collectors.toSet());
308
309 if (rightAnyObjects.isEmpty()) {
310 throw new IllegalArgumentException("Could not find any object for " + cond.getAnyObject());
311 }
312
313 return rightAnyObjects;
314 }
315
316 protected Set<String> check(final MemberCond cond) {
317 Set<String> members = cond.getMember() == null
318 ? Set.of()
319 : SyncopeConstants.UUID_PATTERN.matcher(cond.getMember()).matches()
320 ? Set.of(cond.getMember())
321 : Optional.ofNullable(userDAO.findKey(cond.getMember())).map(Set::of).
322 orElseGet(() -> anyObjectDAO.findByName(cond.getMember()).stream().
323 map(AnyObject::getKey).collect(Collectors.toSet()));
324
325 if (members.isEmpty()) {
326 throw new IllegalArgumentException("Could not find user or any object for " + cond.getMember());
327 }
328
329 return members;
330 }
331
332 @SuppressWarnings("unchecked")
333 protected <T extends Any<?>> List<T> buildResult(final List<Object> raw, final AnyTypeKind kind) {
334 List<String> keys = raw.stream().
335 map(key -> key instanceof Object[] ? (String) ((Object[]) key)[0] : ((String) key)).
336 collect(Collectors.toList());
337
338
339 List<Any<?>> anys = anyUtilsFactory.getInstance(kind).dao().findByKeys(keys).stream().
340 sorted(Comparator.comparing(any -> keys.indexOf(any.getKey()))).collect(Collectors.toList());
341
342 keys.stream().filter(key -> !anys.stream().anyMatch(any -> key.equals(any.getKey()))).
343 forEach(key -> LOG.error("Could not find {} with id {}, even if returned by native query", kind, key));
344
345 return (List<T>) anys;
346 }
347
348 @Override
349 public <T extends Any<?>> List<T> search(
350 final Realm base,
351 final boolean recursive,
352 final Set<String> adminRealms,
353 final SearchCond cond,
354 final int page,
355 final int itemsPerPage,
356 final List<OrderByClause> orderBy,
357 final AnyTypeKind kind) {
358
359 if (CollectionUtils.isEmpty(adminRealms)) {
360 LOG.error("No realms provided");
361 return List.of();
362 }
363
364 LOG.debug("Search condition:\n{}", cond);
365 if (cond == null || !cond.isValid()) {
366 LOG.error("Invalid search condition:\n{}", cond);
367 return List.of();
368 }
369
370 List<OrderByClause> effectiveOrderBy;
371 if (orderBy.isEmpty()) {
372 OrderByClause keyClause = new OrderByClause();
373 keyClause.setField(kind == AnyTypeKind.USER ? "username" : "name");
374 keyClause.setDirection(OrderByClause.Direction.ASC);
375 effectiveOrderBy = List.of(keyClause);
376 } else {
377 effectiveOrderBy = orderBy.stream().
378 filter(clause -> !ArrayUtils.contains(ORDER_BY_NOT_ALLOWED, clause.getField())).
379 collect(Collectors.toList());
380 }
381
382 return doSearch(base, recursive, adminRealms, cond, page, itemsPerPage, effectiveOrderBy, kind);
383 }
384 }