blob: c28b6ff019cfbb1d481e1914dd12fea5241da5a0 [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.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import javax.persistence.DiscriminatorValue;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.to.PropagationTaskTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.ExecStatus;
import org.apache.syncope.common.lib.types.TaskType;
import org.apache.syncope.core.persistence.api.dao.RemediationDAO;
import org.apache.syncope.core.persistence.api.dao.TaskDAO;
import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
import org.apache.syncope.core.persistence.api.entity.Entity;
import org.apache.syncope.core.persistence.api.entity.Implementation;
import org.apache.syncope.core.persistence.api.entity.Notification;
import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
import org.apache.syncope.core.persistence.api.entity.task.PullTask;
import org.apache.syncope.core.persistence.api.entity.task.PushTask;
import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
import org.apache.syncope.core.persistence.api.entity.task.Task;
import org.apache.syncope.core.persistence.jpa.entity.task.JPANotificationTask;
import org.apache.syncope.core.persistence.jpa.entity.task.JPAPropagationTask;
import org.apache.syncope.core.persistence.jpa.entity.task.JPAPushTask;
import org.apache.syncope.core.persistence.jpa.entity.task.JPASchedTask;
import org.apache.syncope.core.persistence.jpa.entity.task.JPAPullTask;
import org.apache.syncope.core.persistence.jpa.entity.task.AbstractTask;
import org.apache.syncope.core.persistence.jpa.entity.task.JPATaskExec;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
public class JPATaskDAO extends AbstractDAO<Task> implements TaskDAO {
protected static String getEntityTableName(final TaskType type) {
String result = null;
switch (type) {
case NOTIFICATION:
result = JPANotificationTask.class.getAnnotation(DiscriminatorValue.class).value();
break;
case PROPAGATION:
result = JPAPropagationTask.class.getAnnotation(DiscriminatorValue.class).value();
break;
case PUSH:
result = JPAPushTask.class.getAnnotation(DiscriminatorValue.class).value();
break;
case SCHEDULED:
result = JPASchedTask.class.getAnnotation(DiscriminatorValue.class).value();
break;
case PULL:
result = JPAPullTask.class.getAnnotation(DiscriminatorValue.class).value();
break;
default:
}
return result;
}
protected final RemediationDAO remediationDAO;
public JPATaskDAO(final RemediationDAO remediationDAO) {
this.remediationDAO = remediationDAO;
}
@Override
public Class<? extends Task> getEntityReference(final TaskType type) {
Class<? extends Task> result = null;
switch (type) {
case NOTIFICATION:
result = JPANotificationTask.class;
break;
case PROPAGATION:
result = JPAPropagationTask.class;
break;
case PUSH:
result = JPAPushTask.class;
break;
case SCHEDULED:
result = JPASchedTask.class;
break;
case PULL:
result = JPAPullTask.class;
break;
default:
}
return result;
}
@Transactional(readOnly = true)
@SuppressWarnings("unchecked")
@Override
public <T extends Task> T find(final String key) {
return (T) entityManager().find(AbstractTask.class, key);
}
@Override
public List<SchedTask> findByDelegate(final Implementation delegate) {
TypedQuery<SchedTask> query = entityManager().createQuery(
"SELECT e FROM " + JPASchedTask.class.getSimpleName()
+ " e WHERE e.jobDelegate=:delegate", SchedTask.class);
query.setParameter("delegate", delegate);
return query.getResultList();
}
@Override
public List<PullTask> findByReconFilterBuilder(final Implementation reconFilterBuilder) {
TypedQuery<PullTask> query = entityManager().createQuery(
"SELECT e FROM " + JPAPullTask.class.getSimpleName()
+ " e WHERE e.reconFilterBuilder=:reconFilterBuilder", PullTask.class);
query.setParameter("reconFilterBuilder", reconFilterBuilder);
return query.getResultList();
}
@Override
public List<PullTask> findByPullActions(final Implementation pullActions) {
TypedQuery<PullTask> query = entityManager().createQuery(
"SELECT e FROM " + JPAPullTask.class.getSimpleName() + " e "
+ "WHERE :pullActions MEMBER OF e.actions", PullTask.class);
query.setParameter("pullActions", pullActions);
return query.getResultList();
}
@Override
public List<PushTask> findByPushActions(final Implementation pushActions) {
TypedQuery<PushTask> query = entityManager().createQuery(
"SELECT e FROM " + JPAPushTask.class.getSimpleName() + " e "
+ "WHERE :pushActions MEMBER OF e.actions", PushTask.class);
query.setParameter("pushActions", pushActions);
return query.getResultList();
}
protected final <T extends Task> StringBuilder buildFindAllQueryJPA(final TaskType type) {
StringBuilder builder = new StringBuilder("SELECT t FROM ").
append(getEntityReference(type).getSimpleName()).
append(" t WHERE ");
if (type == TaskType.SCHEDULED) {
builder.append("t.id NOT IN (SELECT t.id FROM ").
append(JPAPushTask.class.getSimpleName()).append(" t) ").
append("AND ").
append("t.id NOT IN (SELECT t.id FROM ").
append(JPAPullTask.class.getSimpleName()).append(" t)");
} else {
builder.append("1=1");
}
return builder.append(' ');
}
@Override
@SuppressWarnings("unchecked")
public <T extends Task> List<T> findToExec(final TaskType type) {
StringBuilder queryString = buildFindAllQueryJPA(type).append("AND ");
if (type == TaskType.NOTIFICATION) {
queryString.append("t.executed = false ");
} else {
queryString.append("t.executions IS EMPTY ");
}
queryString.append("ORDER BY t.id DESC");
Query query = entityManager().createQuery(queryString.toString());
return query.getResultList();
}
@Transactional(readOnly = true)
@Override
public <T extends Task> List<T> findAll(final TaskType type) {
return findAll(type, null, null, null, null, -1, -1, List.of());
}
protected StringBuilder buildFindAllQuery(
final TaskType type,
final ExternalResource resource,
final Notification notification,
final AnyTypeKind anyTypeKind,
final String entityKey,
final boolean orderByTaskExecInfo,
final List<Object> queryParameters) {
if (resource != null
&& type != TaskType.PROPAGATION && type != TaskType.PUSH && type != TaskType.PULL) {
throw new IllegalArgumentException(type + " is not related to " + ExternalResource.class.getSimpleName());
}
if ((anyTypeKind != null || entityKey != null)
&& type != TaskType.PROPAGATION && type != TaskType.NOTIFICATION) {
throw new IllegalArgumentException(type + " is not related to users, groups or any objects");
}
if (notification != null && type != TaskType.NOTIFICATION) {
throw new IllegalArgumentException(type + " is not related to notifications");
}
StringBuilder queryString = new StringBuilder("SELECT ").append(AbstractTask.TABLE).append(".*");
if (orderByTaskExecInfo) {
queryString.append(',').append(JPATaskExec.TABLE).append(".startDate AS startDate").
append(',').append(JPATaskExec.TABLE).append(".endDate AS endDate").
append(',').append(JPATaskExec.TABLE).append(".status AS status").
append(" FROM ").append(AbstractTask.TABLE).
append(',').append(JPATaskExec.TABLE).append(',').append("(SELECT ").
append(JPATaskExec.TABLE).append(".task_id, ").
append("MAX(").append(JPATaskExec.TABLE).append(".startDate) AS startDate").
append(" FROM ").append(JPATaskExec.TABLE).
append(" GROUP BY ").append(JPATaskExec.TABLE).append(".task_id) GRP").
append(" WHERE ").
append(AbstractTask.TABLE).append(".id=").append(JPATaskExec.TABLE).append(".task_id").
append(" AND ").append(AbstractTask.TABLE).append(".id=").append("GRP.task_id").
append(" AND ").append(JPATaskExec.TABLE).append(".startDate=").append("GRP.startDate").
append(" AND ").append(AbstractTask.TABLE).append(".DTYPE = ?1");
} else {
queryString.append(", null AS startDate, null AS endDate, null AS status FROM ").append(AbstractTask.TABLE).
append(" WHERE ").append(AbstractTask.TABLE).append(".DTYPE = ?1");
}
queryParameters.add(getEntityTableName(type));
if (type == TaskType.SCHEDULED) {
queryString.append(" AND ").
append(AbstractTask.TABLE).
append(".id NOT IN (SELECT ").append(AbstractTask.TABLE).append(".id FROM ").
append(AbstractTask.TABLE).append(" WHERE ").
append(AbstractTask.TABLE).append(".DTYPE = ?2)").
append(" AND ").
append(AbstractTask.TABLE).
append(".id NOT IN (SELECT id FROM ").
append(AbstractTask.TABLE).append(" WHERE ").
append(AbstractTask.TABLE).append(".DTYPE = ?3)");
queryParameters.add(JPAPushTask.class.getAnnotation(DiscriminatorValue.class).value());
queryParameters.add(JPAPullTask.class.getAnnotation(DiscriminatorValue.class).value());
}
queryString.append(' ');
if (resource != null) {
queryParameters.add(resource.getKey());
queryString.append(" AND ").
append(AbstractTask.TABLE).
append(".resource_id=?").append(queryParameters.size());
}
if (notification != null) {
queryParameters.add(notification.getKey());
queryString.append(" AND ").
append(AbstractTask.TABLE).
append(".notification_id=?").append(queryParameters.size());
}
if (anyTypeKind != null && entityKey != null) {
queryParameters.add(anyTypeKind.name());
queryParameters.add(entityKey);
queryString.append(" AND ").
append(AbstractTask.TABLE).
append(".anyTypeKind=?").append(queryParameters.size() - 1).
append(" AND ").
append(AbstractTask.TABLE).
append(".entityKey=?").append(queryParameters.size());
}
return queryString;
}
protected String toOrderByStatement(
final Class<? extends Task> beanClass,
final List<OrderByClause> orderByClauses) {
StringBuilder statement = new StringBuilder();
statement.append(" ORDER BY ");
StringBuilder subStatement = new StringBuilder();
orderByClauses.forEach(clause -> {
String field = clause.getField().trim();
switch (field) {
case "latestExecStatus":
field = "status";
break;
case "start":
field = "startDate";
break;
case "end":
field = "endDate";
break;
default:
Field beanField = ReflectionUtils.findField(beanClass, field);
if (beanField != null
&& (beanField.getAnnotation(ManyToOne.class) != null
|| beanField.getAnnotation(OneToMany.class) != null)) {
field += "_id";
}
}
subStatement.append(field).append(' ').append(clause.getDirection().name()).append(',');
});
if (subStatement.length() == 0) {
statement.append("id DESC");
} else {
subStatement.deleteCharAt(subStatement.length() - 1);
statement.append(subStatement);
}
return statement.toString();
}
@Override
public <T extends Task> List<T> findAll(
final TaskType type,
final ExternalResource resource,
final Notification notification,
final AnyTypeKind anyTypeKind,
final String entityKey,
final int page,
final int itemsPerPage,
final List<OrderByClause> orderByClauses) {
List<Object> queryParameters = new ArrayList<>();
boolean orderByTaskExecInfo = orderByClauses.stream().
anyMatch(clause -> clause.getField().equals("start")
|| clause.getField().equals("end")
|| clause.getField().equals("latestExecStatus")
|| clause.getField().equals("status"));
StringBuilder queryString = buildFindAllQuery(
type,
resource,
notification,
anyTypeKind,
entityKey,
orderByTaskExecInfo,
queryParameters);
if (orderByTaskExecInfo) {
// UNION with tasks without executions...
queryString.insert(0, "SELECT T.id FROM ((").append(") UNION ALL (").
append(buildFindAllQuery(
type,
resource,
notification,
anyTypeKind,
entityKey,
false,
queryParameters)).
append(" AND id NOT IN ").
append("(SELECT task_id AS id FROM ").append(JPATaskExec.TABLE).append(')').
append(")) T");
} else {
queryString.insert(0, "SELECT T.id FROM (").append(") T");
}
queryString.append(toOrderByStatement(getEntityReference(type), orderByClauses));
Query query = entityManager().createNativeQuery(queryString.toString());
for (int i = 1; i <= queryParameters.size(); i++) {
query.setParameter(i, queryParameters.get(i - 1));
}
query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
if (itemsPerPage > 0) {
query.setMaxResults(itemsPerPage);
}
List<T> result = new ArrayList<>();
@SuppressWarnings("unchecked")
List<Object> raw = query.getResultList();
raw.stream().map(key -> key instanceof Object[]
? (String) ((Object[]) key)[0]
: ((String) key)).forEach(key -> {
T task = find(key);
if (task == null) {
LOG.error("Could not find task with key {}, even if returned by native query", key);
} else if (!result.contains(task)) {
result.add(task);
}
});
return result;
}
@Override
public int count(
final TaskType type,
final ExternalResource resource,
final Notification notification,
final AnyTypeKind anyTypeKind,
final String entityKey) {
List<Object> queryParameters = new ArrayList<>();
StringBuilder queryString =
buildFindAllQuery(type, resource, notification, anyTypeKind, entityKey, false, queryParameters);
Query query = entityManager().createNativeQuery(StringUtils.replaceOnce(
queryString.toString(),
"SELECT " + AbstractTask.TABLE + ".*, null AS startDate, null AS endDate, null AS status",
"SELECT COUNT(" + AbstractTask.TABLE + ".id)"));
for (int i = 1; i <= queryParameters.size(); i++) {
query.setParameter(i, queryParameters.get(i - 1));
}
return ((Number) query.getSingleResult()).intValue();
}
@Transactional(rollbackFor = { Throwable.class })
@Override
public <T extends Task> T save(final T task) {
return entityManager().merge(task);
}
@Override
public void delete(final String id) {
Task task = find(id);
if (task == null) {
return;
}
delete(task);
}
@Override
public void delete(final Task task) {
if (task instanceof PullTask) {
remediationDAO.findByPullTask((PullTask) task).forEach(remediation -> remediation.setPullTask(null));
}
entityManager().remove(task);
}
@Override
public void deleteAll(final ExternalResource resource, final TaskType type) {
findAll(type, resource, null, null, null, -1, -1, List.of()).
stream().map(Entity::getKey).forEach(this::delete);
}
@Override
public List<PropagationTaskTO> purgePropagations(final Date since, final List<ExecStatus> statuses) {
StringBuilder queryString = new StringBuilder("SELECT t.task_id "
+ "FROM TaskExec t INNER JOIN Task z ON t.task_id=z.id AND z.dtype='PropagationTask' "
+ "WHERE t.enddate=(SELECT MAX(e.enddate) FROM TaskExec e WHERE e.task_id=t.task_id) ");
List<Object> queryParameters = new ArrayList<>();
if (since != null) {
queryParameters.add(since);
queryString.append("AND t.enddate <= ?").append(queryParameters.size()).append(' ');
}
if (!CollectionUtils.isEmpty(statuses)) {
queryString.append("AND (").
append(statuses.stream().map(status -> {
queryParameters.add(status.name());
return "t.status = ?" + queryParameters.size();
}).collect(Collectors.joining(" OR "))).
append(")");
}
Query query = entityManager().createNativeQuery(queryString.toString());
for (int i = 1; i <= queryParameters.size(); i++) {
query.setParameter(i, queryParameters.get(i - 1));
}
@SuppressWarnings("unchecked")
List<Object> raw = query.getResultList();
List<PropagationTaskTO> purged = new ArrayList<>();
raw.stream().map(Object::toString).distinct().forEach(key -> {
PropagationTask task = find(key);
if (task != null) {
PropagationTaskTO taskTO = new PropagationTaskTO();
taskTO.setOperation(task.getOperation());
taskTO.setConnObjectKey(task.getConnObjectKey());
taskTO.setOldConnObjectKey(task.getOldConnObjectKey());
taskTO.setAttributes(task.getSerializedAttributes());
taskTO.setResource(task.getResource().getKey());
taskTO.setObjectClassName(task.getObjectClassName());
taskTO.setAnyTypeKind(task.getAnyTypeKind());
taskTO.setAnyType(task.getAnyType());
taskTO.setEntityKey(task.getEntityKey());
purged.add(taskTO);
delete(task);
}
});
return purged;
}
}