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.reflect.Field;
22 import java.time.OffsetDateTime;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Objects;
26 import java.util.Optional;
27 import java.util.stream.Collectors;
28 import javax.persistence.ManyToOne;
29 import javax.persistence.NoResultException;
30 import javax.persistence.OneToMany;
31 import javax.persistence.OneToOne;
32 import javax.persistence.Query;
33 import javax.persistence.TypedQuery;
34 import org.apache.commons.lang3.StringUtils;
35 import org.apache.syncope.common.lib.to.PropagationTaskTO;
36 import org.apache.syncope.common.lib.types.AnyTypeKind;
37 import org.apache.syncope.common.lib.types.ExecStatus;
38 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
39 import org.apache.syncope.common.lib.types.TaskType;
40 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
41 import org.apache.syncope.core.persistence.api.dao.RemediationDAO;
42 import org.apache.syncope.core.persistence.api.dao.TaskDAO;
43 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
44 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
45 import org.apache.syncope.core.persistence.api.entity.Implementation;
46 import org.apache.syncope.core.persistence.api.entity.Notification;
47 import org.apache.syncope.core.persistence.api.entity.Realm;
48 import org.apache.syncope.core.persistence.api.entity.task.MacroTask;
49 import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
50 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
51 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
52 import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
53 import org.apache.syncope.core.persistence.api.entity.task.Task;
54 import org.apache.syncope.core.persistence.api.entity.task.TaskUtils;
55 import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
56 import org.apache.syncope.core.persistence.jpa.entity.task.JPAMacroTask;
57 import org.apache.syncope.core.persistence.jpa.entity.task.JPANotificationTask;
58 import org.apache.syncope.core.persistence.jpa.entity.task.JPAPropagationTask;
59 import org.apache.syncope.core.persistence.jpa.entity.task.JPAPropagationTaskExec;
60 import org.apache.syncope.core.persistence.jpa.entity.task.JPAPullTask;
61 import org.apache.syncope.core.persistence.jpa.entity.task.JPAPushTask;
62 import org.apache.syncope.core.persistence.jpa.entity.task.JPASchedTask;
63 import org.apache.syncope.core.spring.security.AuthContextUtils;
64 import org.apache.syncope.core.spring.security.SecurityProperties;
65 import org.springframework.transaction.annotation.Transactional;
66 import org.springframework.util.CollectionUtils;
67 import org.springframework.util.ReflectionUtils;
68
69 public class JPATaskDAO extends AbstractDAO<Task<?>> implements TaskDAO {
70
71 protected final RealmDAO realmDAO;
72
73 protected final RemediationDAO remediationDAO;
74
75 protected final TaskUtilsFactory taskUtilsFactory;
76
77 protected final SecurityProperties securityProperties;
78
79 public JPATaskDAO(
80 final RealmDAO realmDAO,
81 final RemediationDAO remediationDAO,
82 final TaskUtilsFactory taskUtilsFactory,
83 final SecurityProperties securityProperties) {
84
85 this.realmDAO = realmDAO;
86 this.remediationDAO = remediationDAO;
87 this.taskUtilsFactory = taskUtilsFactory;
88 this.securityProperties = securityProperties;
89 }
90
91 @Transactional(readOnly = true)
92 @Override
93 public boolean exists(final TaskType type, final String key) {
94 Query query = entityManager().createNativeQuery("SELECT id FROM "
95 + taskUtilsFactory.getInstance(type).getTaskTable()
96 + " WHERE id=?");
97 query.setParameter(1, key);
98
99 return !query.getResultList().isEmpty();
100 }
101
102 @Transactional(readOnly = true)
103 @SuppressWarnings("unchecked")
104 @Override
105 public <T extends Task<T>> T find(final TaskType type, final String key) {
106 return (T) entityManager().find(taskUtilsFactory.getInstance(type).getTaskEntity(), key);
107 }
108
109 @Transactional(readOnly = true)
110 @SuppressWarnings("unchecked")
111 @Override
112 public <T extends SchedTask> Optional<T> findByName(final TaskType type, final String name) {
113 TaskUtils taskUtils = taskUtilsFactory.getInstance(type);
114 TypedQuery<T> query = (TypedQuery<T>) entityManager().createQuery(
115 "SELECT e FROM " + taskUtils.getTaskEntity().getSimpleName() + " e WHERE e.name = :name",
116 taskUtils.getTaskEntity());
117 query.setParameter("name", name);
118
119 try {
120 return Optional.of(query.getSingleResult());
121 } catch (NoResultException e) {
122 LOG.debug("No task found with name {}", name, e);
123 return Optional.empty();
124 }
125 }
126
127 @Override
128 public Optional<Task<?>> find(final String key) {
129 Task<?> task = find(TaskType.SCHEDULED, key);
130 if (task == null) {
131 task = find(TaskType.PULL, key);
132 }
133 if (task == null) {
134 task = find(TaskType.PUSH, key);
135 }
136 if (task == null) {
137 task = find(TaskType.MACRO, key);
138 }
139 if (task == null) {
140 task = find(TaskType.PROPAGATION, key);
141 }
142 if (task == null) {
143 task = find(TaskType.NOTIFICATION, key);
144 }
145
146 return Optional.ofNullable(task);
147 }
148
149 @Override
150 public List<SchedTask> findByDelegate(final Implementation delegate) {
151 TypedQuery<SchedTask> query = entityManager().createQuery(
152 "SELECT e FROM " + JPASchedTask.class.getSimpleName()
153 + " e WHERE e.jobDelegate=:delegate", SchedTask.class);
154 query.setParameter("delegate", delegate);
155
156 return query.getResultList();
157 }
158
159 @Override
160 public List<PullTask> findByReconFilterBuilder(final Implementation reconFilterBuilder) {
161 TypedQuery<PullTask> query = entityManager().createQuery(
162 "SELECT e FROM " + JPAPullTask.class.getSimpleName()
163 + " e WHERE e.reconFilterBuilder=:reconFilterBuilder", PullTask.class);
164 query.setParameter("reconFilterBuilder", reconFilterBuilder);
165
166 return query.getResultList();
167 }
168
169 @Override
170 public List<PullTask> findByPullActions(final Implementation pullActions) {
171 TypedQuery<PullTask> query = entityManager().createQuery(
172 "SELECT e FROM " + JPAPullTask.class.getSimpleName() + " e "
173 + "WHERE :pullActions MEMBER OF e.actions", PullTask.class);
174 query.setParameter("pullActions", pullActions);
175
176 return query.getResultList();
177 }
178
179 @Override
180 public List<PushTask> findByPushActions(final Implementation pushActions) {
181 TypedQuery<PushTask> query = entityManager().createQuery(
182 "SELECT e FROM " + JPAPushTask.class.getSimpleName() + " e "
183 + "WHERE :pushActions MEMBER OF e.actions", PushTask.class);
184 query.setParameter("pushActions", pushActions);
185
186 return query.getResultList();
187 }
188
189 @Override
190 public List<MacroTask> findByRealm(final Realm realm) {
191 TypedQuery<MacroTask> query = entityManager().createQuery(
192 "SELECT e FROM " + JPAMacroTask.class.getSimpleName() + " e "
193 + "WHERE e.realm=:realm", MacroTask.class);
194 query.setParameter("realm", realm);
195
196 return query.getResultList();
197 }
198
199 @Override
200 public List<MacroTask> findByCommand(final Implementation command) {
201 TypedQuery<MacroTask> query = entityManager().createQuery("SELECT e FROM " + JPAMacroTask.class.getSimpleName()
202 + " e WHERE :command MEMBER OF e.commands", MacroTask.class);
203 query.setParameter("command", command);
204
205 return query.getResultList();
206 }
207
208 protected final <T extends Task<T>> StringBuilder buildFindAllQueryJPA(final TaskType type) {
209 StringBuilder builder = new StringBuilder("SELECT t FROM ").
210 append(taskUtilsFactory.getInstance(type).getTaskEntity().getSimpleName()).
211 append(" t WHERE ");
212 if (type == TaskType.SCHEDULED) {
213 builder.append("t.id NOT IN (SELECT t.id FROM ").
214 append(JPAPushTask.class.getSimpleName()).append(" t) ").
215 append("AND ").
216 append("t.id NOT IN (SELECT t.id FROM ").
217 append(JPAPullTask.class.getSimpleName()).append(" t)");
218 } else {
219 builder.append("1=1");
220 }
221
222 return builder.append(' ');
223 }
224
225 @Override
226 @SuppressWarnings("unchecked")
227 public <T extends Task<T>> List<T> findToExec(final TaskType type) {
228 StringBuilder queryString = buildFindAllQueryJPA(type).append("AND ");
229
230 if (type == TaskType.NOTIFICATION) {
231 queryString.append("t.executed = false ");
232 } else {
233 queryString.append("t.executions IS EMPTY ");
234 }
235 queryString.append("ORDER BY t.id DESC");
236
237 Query query = entityManager().createQuery(queryString.toString());
238 return query.getResultList();
239 }
240
241 @Transactional(readOnly = true)
242 @Override
243 public <T extends Task<T>> List<T> findAll(final TaskType type) {
244 return findAll(type, null, null, null, null, -1, -1, List.of());
245 }
246
247 protected int setParameter(final List<Object> parameters, final Object parameter) {
248 parameters.add(parameter);
249 return parameters.size();
250 }
251
252 protected StringBuilder buildFindAllQuery(
253 final TaskType type,
254 final ExternalResource resource,
255 final Notification notification,
256 final AnyTypeKind anyTypeKind,
257 final String entityKey,
258 final boolean orderByTaskExecInfo,
259 final List<Object> parameters) {
260
261 if (resource != null
262 && type != TaskType.PROPAGATION && type != TaskType.PUSH && type != TaskType.PULL) {
263
264 throw new IllegalArgumentException(type + " is not related to " + ExternalResource.class.getSimpleName());
265 }
266
267 if ((anyTypeKind != null || entityKey != null)
268 && type != TaskType.PROPAGATION && type != TaskType.NOTIFICATION) {
269
270 throw new IllegalArgumentException(type + " is not related to users, groups or any objects");
271 }
272
273 if (notification != null && type != TaskType.NOTIFICATION) {
274 throw new IllegalArgumentException(type + " is not related to notifications");
275 }
276
277 String taskTable = taskUtilsFactory.getInstance(type).getTaskTable();
278 StringBuilder queryString = new StringBuilder("SELECT ").append(taskTable).append(".*");
279
280 if (orderByTaskExecInfo) {
281 String taskExecTable = taskUtilsFactory.getInstance(type).getTaskExecTable();
282 queryString.append(',').append(taskExecTable).append(".startDate AS startDate").
283 append(',').append(taskExecTable).append(".endDate AS endDate").
284 append(',').append(taskExecTable).append(".status AS status").
285 append(" FROM ").append(taskTable).
286 append(',').append(taskExecTable).append(',').append("(SELECT ").
287 append(taskExecTable).append(".task_id, ").
288 append("MAX(").append(taskExecTable).append(".startDate) AS startDate").
289 append(" FROM ").append(taskExecTable).
290 append(" GROUP BY ").append(taskExecTable).append(".task_id) GRP").
291 append(" WHERE ").
292 append(taskTable).append(".id=").append(taskExecTable).append(".task_id").
293 append(" AND ").append(taskTable).append(".id=").append("GRP.task_id").
294 append(" AND ").
295 append(taskExecTable).append(".startDate=").append("GRP.startDate");
296 } else {
297 queryString.append(", null AS startDate, null AS endDate, null AS status FROM ").append(taskTable).
298 append(" WHERE 1=1");
299 }
300
301 queryString.append(' ');
302
303 if (resource != null) {
304 queryString.append(" AND ").
305 append(taskTable).append(".resource_id=?").append(setParameter(parameters, resource.getKey()));
306 }
307 if (notification != null) {
308 queryString.append(" AND ").
309 append(taskTable).
310 append(".notification_id=?").append(setParameter(parameters, notification.getKey()));
311 }
312 if (anyTypeKind != null) {
313 queryString.append(" AND ").
314 append(taskTable).append(".anyTypeKind=?").append(setParameter(parameters, anyTypeKind.name()));
315 }
316 if (entityKey != null) {
317 queryString.append(" AND ").
318 append(taskTable).append(".entityKey=?").append(setParameter(parameters, entityKey));
319 }
320 if (type == TaskType.MACRO
321 && !AuthContextUtils.getUsername().equals(securityProperties.getAdminUser())) {
322
323 String realmKeysArg = AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.TASK_LIST).stream().
324 map(realmDAO::findByFullPath).
325 filter(Objects::nonNull).
326 flatMap(r -> realmDAO.findDescendants(r.getFullPath(), null, -1, -1).stream()).
327 map(Realm::getKey).
328 distinct().
329 map(realmKey -> "?" + setParameter(parameters, realmKey)).
330 collect(Collectors.joining(","));
331 queryString.append(" AND ").
332 append(taskTable).append(".realm_id IN (").append(realmKeysArg).append(")");
333 }
334
335 return queryString;
336 }
337
338 protected String toOrderByStatement(
339 final Class<? extends Task<?>> beanClass,
340 final List<OrderByClause> orderByClauses) {
341
342 StringBuilder statement = new StringBuilder();
343
344 statement.append(" ORDER BY ");
345
346 StringBuilder subStatement = new StringBuilder();
347 orderByClauses.forEach(clause -> {
348 String field = clause.getField().trim();
349 switch (field) {
350 case "latestExecStatus":
351 field = "status";
352 break;
353
354 case "start":
355 field = "startDate";
356 break;
357
358 case "end":
359 field = "endDate";
360 break;
361
362 default:
363 Field beanField = ReflectionUtils.findField(beanClass, field);
364 if (beanField != null
365 && (beanField.getAnnotation(ManyToOne.class) != null
366 || beanField.getAnnotation(OneToMany.class) != null
367 || beanField.getAnnotation(OneToOne.class) != null)) {
368
369 field += "_id";
370 }
371 }
372
373 subStatement.append(field).append(' ').append(clause.getDirection().name()).append(',');
374 });
375
376 if (subStatement.length() == 0) {
377 statement.append("id DESC");
378 } else {
379 subStatement.deleteCharAt(subStatement.length() - 1);
380 statement.append(subStatement);
381 }
382
383 return statement.toString();
384 }
385
386 @Override
387 public <T extends Task<T>> List<T> findAll(
388 final TaskType type,
389 final ExternalResource resource,
390 final Notification notification,
391 final AnyTypeKind anyTypeKind,
392 final String entityKey,
393 final int page,
394 final int itemsPerPage,
395 final List<OrderByClause> orderByClauses) {
396
397 List<Object> parameters = new ArrayList<>();
398
399 boolean orderByTaskExecInfo = orderByClauses.stream().
400 anyMatch(clause -> clause.getField().equals("start")
401 || clause.getField().equals("end")
402 || clause.getField().equals("latestExecStatus")
403 || clause.getField().equals("status"));
404
405 StringBuilder queryString = buildFindAllQuery(
406 type,
407 resource,
408 notification,
409 anyTypeKind,
410 entityKey,
411 orderByTaskExecInfo,
412 parameters);
413
414 if (orderByTaskExecInfo) {
415
416 queryString.insert(0, "SELECT T.id FROM ((").append(") UNION ALL (").
417 append(buildFindAllQuery(
418 type,
419 resource,
420 notification,
421 anyTypeKind,
422 entityKey,
423 false,
424 parameters)).
425 append(" AND id NOT IN ").
426 append("(SELECT task_id AS id FROM ").
427 append(taskUtilsFactory.getInstance(type).getTaskExecTable()).
428 append(')').
429 append(")) T");
430 } else {
431 queryString.insert(0, "SELECT T.id FROM (").append(") T");
432 }
433
434 queryString.append(toOrderByStatement(taskUtilsFactory.getInstance(type).getTaskEntity(), orderByClauses));
435
436 Query query = entityManager().createNativeQuery(queryString.toString());
437
438 for (int i = 1; i <= parameters.size(); i++) {
439 query.setParameter(i, parameters.get(i - 1));
440 }
441
442 query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
443
444 if (itemsPerPage > 0) {
445 query.setMaxResults(itemsPerPage);
446 }
447
448 List<T> result = new ArrayList<>();
449
450 @SuppressWarnings("unchecked")
451 List<Object> raw = query.getResultList();
452 raw.stream().map(key -> key instanceof Object[]
453 ? (String) ((Object[]) key)[0]
454 : ((String) key)).forEach(key -> {
455
456 T task = find(type, key);
457 if (task == null) {
458 LOG.error("Could not find task with key {}, even if returned by native query", key);
459 } else if (!result.contains(task)) {
460 result.add(task);
461 }
462 });
463
464 return result;
465 }
466
467 @Override
468 public int count(
469 final TaskType type,
470 final ExternalResource resource,
471 final Notification notification,
472 final AnyTypeKind anyTypeKind,
473 final String entityKey) {
474
475 List<Object> parameters = new ArrayList<>();
476
477 StringBuilder queryString =
478 buildFindAllQuery(type, resource, notification, anyTypeKind, entityKey, false, parameters);
479
480 String table = taskUtilsFactory.getInstance(type).getTaskTable();
481 Query query = entityManager().createNativeQuery(StringUtils.replaceOnce(
482 queryString.toString(),
483 "SELECT " + table + ".*, null AS startDate, null AS endDate, null AS status",
484 "SELECT COUNT(" + table + ".id)"));
485
486 for (int i = 1; i <= parameters.size(); i++) {
487 query.setParameter(i, parameters.get(i - 1));
488 }
489
490 return ((Number) query.getSingleResult()).intValue();
491 }
492
493 @Transactional(rollbackFor = { Throwable.class })
494 @Override
495 public <T extends Task<T>> T save(final T task) {
496 if (task instanceof JPANotificationTask) {
497 ((JPANotificationTask) task).list2json();
498 } else if (task instanceof JPAPushTask) {
499 ((JPAPushTask) task).map2json();
500 }
501 return entityManager().merge(task);
502 }
503
504 @Override
505 public void delete(final TaskType type, final String id) {
506 Task<?> task = find(type, id);
507 if (task == null) {
508 return;
509 }
510
511 delete(task);
512 }
513
514 @Override
515 public void delete(final Task<?> task) {
516 if (task instanceof PullTask) {
517 remediationDAO.findByPullTask((PullTask) task).forEach(remediation -> remediation.setPullTask(null));
518 }
519
520 entityManager().remove(task);
521 }
522
523 @Override
524 public void deleteAll(final ExternalResource resource, final TaskType type) {
525 findAll(type, resource, null, null, null, -1, -1, List.of()).
526 stream().map(Task<?>::getKey).forEach(key -> delete(type, key));
527 }
528
529 @Override
530 public List<PropagationTaskTO> purgePropagations(
531 final OffsetDateTime since,
532 final List<ExecStatus> statuses,
533 final List<ExternalResource> externalResources) {
534
535 StringBuilder queryString = new StringBuilder("SELECT t.task_id "
536 + "FROM " + JPAPropagationTaskExec.TABLE + " t "
537 + "INNER JOIN " + JPAPropagationTask.TABLE + " z "
538 + "ON t.task_id=z.id "
539 + "WHERE t.enddate=(SELECT MAX(e.enddate) FROM " + JPAPropagationTaskExec.TABLE + " e "
540 + "WHERE e.task_id=t.task_id) ");
541
542 List<Object> queryParameters = new ArrayList<>();
543 if (since != null) {
544 queryParameters.add(since);
545 queryString.append("AND t.enddate <= ?").append(queryParameters.size()).append(' ');
546 }
547 if (!CollectionUtils.isEmpty(statuses)) {
548 queryString.append("AND (").
549 append(statuses.stream().map(status -> {
550 queryParameters.add(status.name());
551 return "t.status = ?" + queryParameters.size();
552 }).collect(Collectors.joining(" OR "))).
553 append(")");
554 }
555 if (!CollectionUtils.isEmpty(externalResources)) {
556 queryString.append("AND (").
557 append(externalResources.stream().map(externalResource -> {
558 queryParameters.add(externalResource.getKey());
559 return "z.resource_id = ?" + queryParameters.size();
560 }).collect(Collectors.joining(" OR "))).
561 append(")");
562 }
563
564 Query query = entityManager().createNativeQuery(queryString.toString());
565 for (int i = 1; i <= queryParameters.size(); i++) {
566 query.setParameter(i, queryParameters.get(i - 1));
567 }
568
569 @SuppressWarnings("unchecked")
570 List<Object> raw = query.getResultList();
571
572 List<PropagationTaskTO> purged = new ArrayList<>();
573 raw.stream().map(Object::toString).distinct().forEach(key -> {
574 PropagationTask task = find(TaskType.PROPAGATION, key);
575 if (task != null) {
576 PropagationTaskTO taskTO = new PropagationTaskTO();
577
578 taskTO.setOperation(task.getOperation());
579 taskTO.setConnObjectKey(task.getConnObjectKey());
580 taskTO.setOldConnObjectKey(task.getOldConnObjectKey());
581 taskTO.setPropagationData(task.getSerializedPropagationData());
582 taskTO.setResource(task.getResource().getKey());
583 taskTO.setObjectClassName(task.getObjectClassName());
584 taskTO.setAnyTypeKind(task.getAnyTypeKind());
585 taskTO.setAnyType(task.getAnyType());
586 taskTO.setEntityKey(task.getEntityKey());
587
588 purged.add(taskTO);
589
590 delete(task);
591 }
592 });
593
594 return purged;
595 }
596 }