blob: 6a537d01aa818e834d04140f95168486d4a484b8 [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.dao.impl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.validation.ValidationException;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import org.apache.syncope.common.search.AttributableCond;
import org.apache.syncope.common.search.AttributeCond;
import org.apache.syncope.common.search.EntitlementCond;
import org.apache.syncope.common.search.MembershipCond;
import org.apache.syncope.common.search.NodeCond;
import org.apache.syncope.common.search.ResourceCond;
import org.apache.syncope.common.types.AttributableType;
import org.apache.syncope.common.types.AttributeSchemaType;
import org.apache.syncope.core.persistence.beans.AbstractAttrValue;
import org.apache.syncope.core.persistence.beans.AbstractAttributable;
import org.apache.syncope.core.persistence.beans.AbstractSchema;
import org.apache.syncope.core.persistence.dao.AttributableSearchDAO;
import org.apache.syncope.core.persistence.dao.RoleDAO;
import org.apache.syncope.core.persistence.dao.SchemaDAO;
import org.apache.syncope.core.persistence.dao.UserDAO;
import org.apache.syncope.core.util.AttributableUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.util.ReflectionUtils;
@Repository
public class AttributableSearchDAOImpl extends AbstractDAOImpl implements AttributableSearchDAO {
static final private String EMPTY_ATTR_QUERY = "SELECT subject_id FROM user_search_attr WHERE 1=2";
@Autowired
private UserDAO userDAO;
@Autowired
private RoleDAO roleDAO;
@Autowired
private SchemaDAO schemaDAO;
private String getAdminRolesFilter(final Set<Long> adminRoles, final AttributableUtil attrUtil) {
final StringBuilder adminRolesFilter = new StringBuilder();
if (attrUtil.getType() == AttributableType.USER) {
adminRolesFilter.append("SELECT syncopeUser_id AS subject_id FROM Membership M1 WHERE syncopeRole_id IN (").
append("SELECT syncopeRole_id FROM Membership M2 WHERE M2.syncopeUser_id=M1.syncopeUser_id ").
append("AND syncopeRole_id NOT IN (");
}
adminRolesFilter.append("SELECT id AS ").
append(attrUtil.getType() == AttributableType.USER ? "syncopeRole" : "subject").
append("_id FROM SyncopeRole");
boolean firstRole = true;
for (Long adminRoleId : adminRoles) {
if (firstRole) {
adminRolesFilter.append(" WHERE");
firstRole = false;
} else {
adminRolesFilter.append(attrUtil.getType() == AttributableType.USER ? " OR" : " AND");
}
adminRolesFilter.append(attrUtil.getType() == AttributableType.USER
? " id=" : " id <>").append(adminRoleId);
}
if (attrUtil.getType() == AttributableType.USER) {
adminRolesFilter.append("))");
}
return adminRolesFilter.toString();
}
@Override
public int count(final Set<Long> adminRoles, final NodeCond searchCondition, final AttributableUtil attrUtil) {
List<Object> parameters = Collections.synchronizedList(new ArrayList<Object>());
// 1. get the query string from the search condition
StringBuilder queryString = getQuery(searchCondition, parameters, attrUtil);
// 2. take into account administrative roles
queryString.insert(0, "SELECT u.subject_id FROM (");
queryString.append(") u WHERE subject_id NOT IN (");
queryString.append(getAdminRolesFilter(adminRoles, attrUtil)).append(')');
// 3. prepare the COUNT query
queryString.insert(0, "SELECT COUNT(subject_id) FROM (");
queryString.append(") count_subject_id");
Query countQuery = entityManager.createNativeQuery(queryString.toString());
fillWithParameters(countQuery, parameters);
LOG.debug("Native count query\n{}\nwith parameters\n{}", queryString.toString(), parameters);
int result = ((Number) countQuery.getSingleResult()).intValue();
LOG.debug("Native count query result: {}", result);
return result;
}
@Override
public <T extends AbstractAttributable> List<T> search(final Set<Long> adminRoles, final NodeCond searchCondition,
final AttributableUtil attrUtil) {
return search(adminRoles, searchCondition, -1, -1, attrUtil);
}
@Override
public <T extends AbstractAttributable> List<T> search(final Set<Long> adminRoles, final NodeCond searchCondition,
final int page, final int itemsPerPage, final AttributableUtil attrUtil) {
List<T> result = Collections.<T>emptyList();
if (adminRoles != null && (!adminRoles.isEmpty() || roleDAO.findAll().isEmpty())) {
LOG.debug("Search condition:\n{}", searchCondition);
if (searchCondition != null && searchCondition.isValid()) {
try {
result = doSearch(adminRoles, searchCondition, page, itemsPerPage, attrUtil);
} catch (Exception e) {
LOG.error("While searching for {}", attrUtil.getType(), e);
}
} else {
LOG.error("Invalid search condition:\n{}", searchCondition);
}
}
return result;
}
@Override
public <T extends AbstractAttributable> boolean matches(final T user, final NodeCond searchCondition,
final AttributableUtil attrUtil) {
List<Object> parameters = Collections.synchronizedList(new ArrayList<Object>());
// 1. get the query string from the search condition
StringBuilder queryString = getQuery(searchCondition, parameters, attrUtil);
boolean matches;
if (queryString.length() == 0) {
// Could be empty: got into a role search with a single membership condition ...
matches = false;
} else {
// 2. take into account the passed user
queryString.insert(0, "SELECT u.subject_id FROM (");
queryString.append(") u WHERE subject_id=?").append(setParameter(parameters, user.getId()));
// 3. prepare the search query
Query query = entityManager.createNativeQuery(queryString.toString());
// 4. populate the search query with parameter values
fillWithParameters(query, parameters);
// 5. executes query
matches = !query.getResultList().isEmpty();
}
return matches;
}
private int setParameter(final List<Object> parameters, final Object parameter) {
int key;
synchronized (parameters) {
parameters.add(parameter);
key = parameters.size();
}
return key;
}
private void fillWithParameters(final Query query, final List<Object> parameters) {
for (int i = 0; i < parameters.size(); i++) {
if (parameters.get(i) instanceof Date) {
query.setParameter(i + 1, (Date) parameters.get(i), TemporalType.TIMESTAMP);
} else if (parameters.get(i) instanceof Boolean) {
query.setParameter(i + 1, ((Boolean) parameters.get(i))
? 1
: 0);
} else {
query.setParameter(i + 1, parameters.get(i));
}
}
}
@SuppressWarnings("unchecked")
private <T extends AbstractAttributable> List<T> doSearch(final Set<Long> adminRoles, final NodeCond nodeCond,
final int page, final int itemsPerPage, final AttributableUtil attrUtil) {
List<Object> parameters = Collections.synchronizedList(new ArrayList<Object>());
// 1. get the query string from the search condition
final StringBuilder queryString = getQuery(nodeCond, parameters, attrUtil);
// 2. take into account administrative roles
if (queryString.charAt(0) == '(') {
queryString.insert(0, "SELECT u.subject_id FROM ");
queryString.append(" u WHERE subject_id NOT IN (");
} else {
queryString.insert(0, "SELECT u.subject_id FROM (");
queryString.append(") u WHERE subject_id NOT IN (");
}
queryString.append(getAdminRolesFilter(adminRoles, attrUtil)).append(")");
// 3. prepare the search query
final Query query = entityManager.createNativeQuery(queryString.toString());
// 4. page starts from 1, while setFirtResult() starts from 0
query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
if (itemsPerPage >= 0) {
query.setMaxResults(itemsPerPage);
}
// 5. populate the search query with parameter values
fillWithParameters(query, parameters);
LOG.debug("Native query\n{}\nwith parameters\n{}", queryString.toString(), parameters);
// 6. Prepare the result (avoiding duplicates)
final List<Number> subjectIds = new ArrayList<Number>();
final List resultList = query.getResultList();
// fix for HHH-5902 - bug hibernate
if (resultList != null) {
for (Object userId : resultList) {
final Number subjectIdNumber;
if (userId instanceof Object[]) {
subjectIdNumber = (Number) ((Object[]) userId)[0];
} else {
subjectIdNumber = (Number) userId;
}
if (!subjectIds.contains(subjectIdNumber)) {
subjectIds.add(subjectIdNumber);
}
}
}
final List<T> result = new ArrayList<T>(subjectIds.size());
for (Number subjectId : subjectIds) {
T subject = attrUtil.getType() == AttributableType.USER
? (T) userDAO.find(subjectId.longValue())
: (T) roleDAO.find(subjectId.longValue());
if (subject == null) {
LOG.error("Could not find {} with id {}, even though returned by the native query",
attrUtil.getType(), subjectId);
} else {
result.add(subject);
}
}
return result;
}
private StringBuilder getQuery(final NodeCond nodeCond, final List<Object> parameters,
final AttributableUtil attrUtil) {
StringBuilder query = new StringBuilder();
switch (nodeCond.getType()) {
case LEAF:
case NOT_LEAF:
if (nodeCond.getMembershipCond() != null && AttributableType.USER == attrUtil.getType()) {
query.append(getQuery(nodeCond.getMembershipCond(), nodeCond.getType() == NodeCond.Type.NOT_LEAF,
parameters, attrUtil));
}
if (nodeCond.getResourceCond() != null) {
query.append(getQuery(nodeCond.getResourceCond(), nodeCond.getType() == NodeCond.Type.NOT_LEAF,
parameters, attrUtil));
}
if (nodeCond.getEntitlementCond() != null) {
query.append(getQuery(nodeCond.getEntitlementCond(), nodeCond.getType() == NodeCond.Type.NOT_LEAF,
parameters));
}
if (nodeCond.getAttributeCond() != null) {
query.append(getQuery(nodeCond.getAttributeCond(), nodeCond.getType() == NodeCond.Type.NOT_LEAF,
parameters, attrUtil));
}
if (nodeCond.getAttributableCond() != null) {
query.append(getQuery(nodeCond.getAttributableCond(), nodeCond.getType() == NodeCond.Type.NOT_LEAF,
parameters, attrUtil));
}
break;
case AND:
query.append(getQuery(nodeCond.getLeftNodeCond(), parameters, attrUtil)).
append(" AND subject_id IN ( ").
append(getQuery(nodeCond.getRightNodeCond(), parameters, attrUtil).
append(")"));
break;
case OR:
query.append(getQuery(nodeCond.getLeftNodeCond(), parameters, attrUtil)).
append(" UNION ").
append(getQuery(nodeCond.getRightNodeCond(), parameters, attrUtil));
break;
default:
}
return query;
}
private String getQuery(final MembershipCond cond, final boolean not, final List<Object> parameters,
final AttributableUtil attrUtil) {
StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM ").
append(attrUtil.searchView()).append(" WHERE ");
if (not) {
query.append("subject_id NOT IN (");
} else {
query.append("subject_id IN (");
}
query.append("SELECT DISTINCT subject_id ").append("FROM ").
append(attrUtil.searchView()).append("_membership WHERE ");
if (cond.getRoleId() != null) {
query.append("role_id=?").append(setParameter(parameters, cond.getRoleId()));
} else if (cond.getRoleName() != null) {
query.append("role_name=?").append(setParameter(parameters, cond.getRoleName()));
}
query.append(')');
return query.toString();
}
private String getQuery(final ResourceCond cond, final boolean not, final List<Object> parameters,
final AttributableUtil attrUtil) {
final StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM ").
append(attrUtil.searchView()).append(" WHERE ");
if (not) {
query.append("subject_id NOT IN (");
} else {
query.append("subject_id IN (");
}
query.append("SELECT DISTINCT subject_id ").append("FROM ").append(attrUtil.searchView()).
append("_resource WHERE resource_name=?").
append(setParameter(parameters, cond.getResourceName()));
if (attrUtil.getType() == AttributableType.USER) {
query.append(" UNION SELECT DISTINCT subject_id ").append("FROM ").append(attrUtil.searchView()).
append("_role_resource WHERE resource_name=?").
append(setParameter(parameters, cond.getResourceName()));
}
query.append(')');
return query.toString();
}
private String getQuery(final EntitlementCond cond, final boolean not, final List<Object> parameters) {
final StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM ").
append("role_search_entitlements WHERE entitlement_name ");
if (not) {
query.append(" NOT ");
}
query.append(" LIKE ?").append(setParameter(parameters, cond.getExpression()));
return query.toString();
}
private void fillAttributeQuery(final StringBuilder query, final AbstractAttrValue attrValue,
final AbstractSchema schema, final AttributeCond cond, final boolean not, final List<Object> parameters) {
String column = (cond instanceof AttributableCond)
? cond.getSchema()
: "' AND " + getFieldName(schema.getType());
switch (cond.getType()) {
case ISNULL:
query.append(column).append(not
? " IS NOT NULL"
: " IS NULL");
break;
case ISNOTNULL:
query.append(column).append(not
? " IS NULL"
: " IS NOT NULL");
break;
case LIKE:
if (schema.getType() == AttributeSchemaType.String || schema.getType() == AttributeSchemaType.Enum) {
query.append(column);
if (not) {
query.append(" NOT ");
}
query.append(" LIKE ?").append(setParameter(parameters, cond.getExpression()));
} else {
if (!(cond instanceof AttributableCond)) {
query.append("' AND");
}
query.append(" 1=2");
LOG.error("LIKE is only compatible with string or enum schemas");
}
break;
case EQ:
query.append(column);
if (not) {
query.append("<>");
} else {
query.append("=");
}
query.append("?").append(setParameter(parameters, attrValue.getValue()));
break;
case GE:
query.append(column);
if (not) {
query.append("<");
} else {
query.append(">=");
}
query.append("?").append(setParameter(parameters, attrValue.getValue()));
break;
case GT:
query.append(column);
if (not) {
query.append("<=");
} else {
query.append(">");
}
query.append("?").append(setParameter(parameters, attrValue.getValue()));
break;
case LE:
query.append(column);
if (not) {
query.append(">");
} else {
query.append("<=");
}
query.append("?").append(setParameter(parameters, attrValue.getValue()));
break;
case LT:
query.append(column);
if (not) {
query.append(">=");
} else {
query.append("<");
}
query.append("?").append(setParameter(parameters, attrValue.getValue()));
break;
default:
}
}
private String getFieldName(final AttributeSchemaType type) {
String result;
switch (type) {
case Boolean:
result = "booleanvalue";
break;
case Date:
result = "datevalue";
break;
case Double:
result = "doublevalue";
break;
case Long:
result = "longvalue";
break;
case String:
case Enum:
result = "stringvalue";
break;
default:
result = null;
}
return result;
}
private String getQuery(final AttributeCond cond, final boolean not, final List<Object> parameters,
final AttributableUtil attrUtil) {
AbstractSchema schema = schemaDAO.find(cond.getSchema(), attrUtil.schemaClass());
if (schema == null) {
LOG.warn("Ignoring invalid schema '{}'", cond.getSchema());
return EMPTY_ATTR_QUERY;
}
AbstractAttrValue attrValue = attrUtil.newAttrValue();
try {
if (cond.getType() != AttributeCond.Type.LIKE && cond.getType() != AttributeCond.Type.ISNULL
&& cond.getType() != AttributeCond.Type.ISNOTNULL) {
schema.getValidator().validate(cond.getExpression(), attrValue);
}
} catch (ValidationException e) {
LOG.error("Could not validate expression '" + cond.getExpression() + "'", e);
return EMPTY_ATTR_QUERY;
}
StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM ").append(attrUtil.searchView());
if (cond.getType() == AttributeCond.Type.ISNOTNULL) {
query.append(" WHERE subject_id NOT IN (SELECT subject_id FROM ").
append(attrUtil.searchView()).append("_null_attr WHERE schema_name='").
append(schema.getName()).append("')");
} else {
if (cond.getType() == AttributeCond.Type.ISNULL) {
query.append("_null_attr WHERE schema_name='").append(schema.getName()).append("'");
} else {
if (schema.isUniqueConstraint()) {
query.append("_unique_attr ");
} else {
query.append("_attr ");
}
query.append("WHERE schema_name='").append(schema.getName());
fillAttributeQuery(query, attrValue, schema, cond, not, parameters);
}
}
return query.toString();
}
@SuppressWarnings("rawtypes")
private String getQuery(final AttributableCond cond, final boolean not, final List<Object> parameters,
final AttributableUtil attrUtil) {
Field attributableField = ReflectionUtils.findField(attrUtil.attributableClass(), cond.getSchema());
if (attributableField == null) {
LOG.warn("Ignoring invalid schema '{}'", cond.getSchema());
return EMPTY_ATTR_QUERY;
}
AbstractSchema schema = attrUtil.newSchema();
schema.setName(attributableField.getName());
for (AttributeSchemaType type : AttributeSchemaType.values()) {
if (attributableField.getType().isAssignableFrom(type.getType())) {
schema.setType(type);
}
}
// Deal with Attributable Integer fields logically mapping to boolean values
// (SyncopeRole.inheritAttributes, for example)
boolean foundBooleanMin = false;
boolean foundBooleanMax = false;
if (Integer.class.equals(attributableField.getType())) {
for (Annotation annotation : attributableField.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) {
if ("true".equalsIgnoreCase(cond.getExpression())) {
cond.setExpression("1");
schema.setType(AttributeSchemaType.Long);
} else if ("false".equalsIgnoreCase(cond.getExpression())) {
cond.setExpression("0");
schema.setType(AttributeSchemaType.Long);
}
}
// Deal with Attributable fields representing relationships to other entities
// Only _id and _name are suppored
if (attributableField.getType().getAnnotation(Entity.class) != null) {
if (BeanUtils.findDeclaredMethodWithMinimalParameters(attributableField.getType(), "getId") != null) {
cond.setSchema(cond.getSchema() + "_id");
schema.setType(AttributeSchemaType.Long);
}
if (BeanUtils.findDeclaredMethodWithMinimalParameters(attributableField.getType(), "getName") != null) {
cond.setSchema(cond.getSchema() + "_name");
schema.setType(AttributeSchemaType.String);
}
}
AbstractAttrValue attrValue = attrUtil.newAttrValue();
try {
if (cond.getType() != AttributeCond.Type.LIKE && cond.getType() != AttributeCond.Type.ISNULL
&& cond.getType() != AttributeCond.Type.ISNOTNULL) {
schema.getValidator().validate(cond.getExpression(), attrValue);
}
} catch (ValidationException e) {
LOG.error("Could not validate expression '" + cond.getExpression() + "'", e);
return EMPTY_ATTR_QUERY;
}
final StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM ").
append(attrUtil.searchView()).append(" WHERE ");
fillAttributeQuery(query, attrValue, schema, cond, not, parameters);
return query.toString();
}
}