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.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         // Keeps track of difference between entity's getKey() and JPA @Id fields
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         // Deal with any Integer fields logically mapping to boolean values
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         // Deal with any fields representing relationships to other entities
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         // sort anys according to keys' sorting, as their ordering is same as raw, e.g. the actual sql query results
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 }