org.syncope.core.persistence.dao.impl.UserSearchDAOImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.syncope.core.persistence.dao.impl.UserSearchDAOImpl.java

Source

/*
 * 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.syncope.core.persistence.dao.impl;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.validation.ValidationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.syncope.client.search.AttributeCond;
import org.syncope.client.search.SyncopeUserCond;
import org.syncope.client.search.MembershipCond;
import org.syncope.client.search.NodeCond;
import org.syncope.client.search.ResourceCond;
import org.syncope.core.persistence.beans.user.SyncopeUser;
import org.syncope.core.persistence.beans.user.UAttrValue;
import org.syncope.core.persistence.beans.user.USchema;
import org.syncope.core.persistence.dao.SchemaDAO;
import org.syncope.core.persistence.dao.UserDAO;
import org.syncope.core.persistence.dao.UserSearchDAO;
import org.syncope.types.SchemaType;

@Repository
public class UserSearchDAOImpl extends AbstractDAOImpl implements UserSearchDAO {

    static final private String EMPTY_ATTR_QUERY = "SELECT user_id FROM user_search_attr WHERE 1=2";

    @Autowired
    private UserDAO userDAO;

    @Autowired
    private SchemaDAO schemaDAO;

    private final Random random;

    public UserSearchDAOImpl() {
        super();

        random = new Random(Calendar.getInstance().getTimeInMillis());
    }

    private String getAdminRolesFilter(final Set<Long> adminRoles) {
        final StringBuilder adminRolesFilter = new StringBuilder();
        if (adminRoles == null || adminRoles.isEmpty()) {
            adminRolesFilter.append("SELECT syncopeUser_id AS user_id ").append("FROM Membership");
        } else {
            adminRolesFilter.append("SELECT syncopeUser_id AS user_id ").append("FROM Membership M1 ")
                    .append("WHERE syncopeRole_id IN (");
            adminRolesFilter.append("SELECT syncopeRole_id ").append("FROM Membership M2 ")
                    .append("WHERE M2.syncopeUser_id=M1.syncopeUser_id ").append("AND syncopeRole_id NOT IN (");
            adminRolesFilter.append("SELECT id AS syncopeRole_id FROM SyncopeRole");
            boolean firstRole = true;
            for (Long adminRoleId : adminRoles) {
                if (firstRole) {
                    adminRolesFilter.append(" WHERE");
                    firstRole = false;
                } else {
                    adminRolesFilter.append(" OR");
                }

                adminRolesFilter.append(" id=").append(adminRoleId);
            }
            adminRolesFilter.append("))");
        }

        return adminRolesFilter.toString();
    }

    @Override
    public int count(final Set<Long> adminRoles, final NodeCond searchCondition) {

        List<Object> parameters = Collections.synchronizedList(new ArrayList<Object>());

        // 1. get the query string from the search condition
        StringBuilder queryString = getQuery(searchCondition, parameters);

        // 2. take into account administrative roles
        queryString.insert(0, "SELECT u.user_id FROM (");
        queryString.append(") u WHERE user_id NOT IN (");
        queryString.append(getAdminRolesFilter(adminRoles)).append(")");

        // 3. prepare the COUNT query
        queryString.insert(0, "SELECT COUNT(user_id) FROM (");
        queryString.append(") count_user_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 List<SyncopeUser> search(final NodeCond searchCondition) {
        return search(null, searchCondition, -1, -1);
    }

    @Override
    public List<SyncopeUser> search(final Set<Long> adminRoles, final NodeCond searchCondition) {

        return search(adminRoles, searchCondition, -1, -1);
    }

    @Override
    public List<SyncopeUser> search(final Set<Long> adminRoles, final NodeCond searchCondition, final int page,
            final int itemsPerPage) {

        List<SyncopeUser> result = Collections.EMPTY_LIST;

        LOG.debug("Search condition:\n{}", searchCondition);
        if (!searchCondition.checkValidity()) {
            LOG.error("Invalid search condition:\n{}", searchCondition);
        } else {
            try {
                result = doSearch(adminRoles, searchCondition, page, itemsPerPage);
            } catch (Throwable t) {
                LOG.error("While searching users", t);
            }
        }

        return result;
    }

    @Override
    public boolean matches(final SyncopeUser user, final NodeCond searchCondition) {

        List<Object> parameters = Collections.synchronizedList(new ArrayList<Object>());

        // 1. get the query string from the search condition
        StringBuilder queryString = getQuery(searchCondition, parameters);

        // 2. take into account the passed user
        queryString.insert(0, "SELECT u.user_id FROM (");
        queryString.append(") u WHERE user_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
        List<SyncopeUser> result = query.getResultList();

        return !result.isEmpty();
    }

    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));
            }
        }
    }

    private List<SyncopeUser> doSearch(final Set<Long> adminRoles, final NodeCond nodeCond, final int page,
            final int itemsPerPage) {

        List<Object> parameters = Collections.synchronizedList(new ArrayList<Object>());

        // 1. get the query string from the search condition
        final StringBuilder queryString = getQuery(nodeCond, parameters);

        // 2. take into account administrative roles
        if (queryString.charAt(0) == '(') {
            queryString.insert(0, "SELECT u.user_id FROM ");
            queryString.append(" u WHERE user_id NOT IN (");
        } else {
            queryString.insert(0, "SELECT u.user_id FROM (");
            queryString.append(") u WHERE user_id NOT IN (");
        }
        queryString.append(getAdminRolesFilter(adminRoles)).append(")");

        // 3. prepare the search query
        final Query query = entityManager.createNativeQuery(queryString.toString());

        // page starts from 1, while setFirtResult() starts from 0
        query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));

        if (itemsPerPage >= 0) {
            query.setMaxResults(itemsPerPage);
        }

        // 4. populate the search query with parameter values
        fillWithParameters(query, parameters);

        LOG.debug("Native query\n{}\nwith parameters\n{}", queryString.toString(), parameters);

        // 5. Prepare the result (avoiding duplicates - set)
        final Set<Number> userIds = new HashSet<Number>();
        final List resultList = query.getResultList();

        //fix for HHH-5902 - bug hibernate
        if (resultList != null) {
            for (Object userId : resultList) {
                if (userId instanceof Object[]) {
                    userIds.add((Number) ((Object[]) userId)[0]);
                } else {
                    userIds.add((Number) userId);
                }
            }
        }

        final List<SyncopeUser> result = new ArrayList<SyncopeUser>(userIds.size());

        SyncopeUser user;
        for (Object userId : userIds) {
            user = userDAO.find(((Number) userId).longValue());
            if (user == null) {
                LOG.error("Could not find user with id {}, " + "even though returned by the native query", userId);
            } else {
                result.add(user);
            }
        }

        return result;
    }

    private StringBuilder getQuery(final NodeCond nodeCond, final List<Object> parameters) {

        StringBuilder query = new StringBuilder();

        switch (nodeCond.getType()) {

        case LEAF:
        case NOT_LEAF:
            if (nodeCond.getMembershipCond() != null) {
                query.append(getQuery(nodeCond.getMembershipCond(), nodeCond.getType() == NodeCond.Type.NOT_LEAF,
                        parameters));
            }
            if (nodeCond.getResourceCond() != null) {
                query.append(getQuery(nodeCond.getResourceCond(), nodeCond.getType() == NodeCond.Type.NOT_LEAF,
                        parameters));
            }
            if (nodeCond.getAttributeCond() != null) {
                query.append(getQuery(nodeCond.getAttributeCond(), nodeCond.getType() == NodeCond.Type.NOT_LEAF,
                        parameters));
            }
            if (nodeCond.getSyncopeUserCond() != null) {
                query.append(getQuery(nodeCond.getSyncopeUserCond(), nodeCond.getType() == NodeCond.Type.NOT_LEAF,
                        parameters));
            }
            break;

        case AND:
            query.append(getQuery(nodeCond.getLeftNodeCond(), parameters)).append(" AND user_id IN ( ")
                    .append(getQuery(nodeCond.getRightNodeCond(), parameters).append(")"));
            break;

        case OR:
            query.append("(").append(getQuery(nodeCond.getLeftNodeCond(), parameters)).append(" UNION ")
                    .append(getQuery(nodeCond.getRightNodeCond(), parameters).append(")"));
            break;

        default:
        }

        return query;
    }

    private String getQuery(final MembershipCond cond, final boolean not, final List<Object> parameters) {

        StringBuilder query = new StringBuilder("SELECT DISTINCT user_id FROM user_search WHERE ");

        if (not) {
            query.append("user_id NOT IN (");
        } else {
            query.append("user_id IN (");
        }

        query.append("SELECT DISTINCT user_id ").append("FROM user_search_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 StringBuilder query = new StringBuilder("SELECT DISTINCT user_id FROM user_search WHERE ");

        if (not) {
            query.append("user_id NOT IN (");
        } else {
            query.append("user_id IN (");
        }

        query.append("SELECT DISTINCT user_id ").append("FROM user_search_resource WHERE ");

        query.append("resource_name=?").append(setParameter(parameters, cond.getResourceName()));

        query.append(")");

        return query.toString();
    }

    private void fillAttributeQuery(final StringBuilder query, final UAttrValue attrValue, final USchema schema,
            final AttributeCond cond, final boolean not, final List<Object> parameters) {

        String column = (cond instanceof SyncopeUserCond) ? 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() == SchemaType.String || schema.getType() == SchemaType.Enum) {

                query.append(column);
                if (not) {
                    query.append(" NOT ");
                }
                query.append(" LIKE ");
                if (!(cond instanceof SyncopeUserCond)) {
                    query.append('\'');
                }
                query.append(cond.getExpression()).append("'");
            } else {
                if (!(cond instanceof SyncopeUserCond)) {
                    query.append("' AND");
                }
                query.append(" 1=2");
                LOG.error("LIKE is only compatible with string 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 SchemaType 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) {

        USchema schema = schemaDAO.find(cond.getSchema(), USchema.class);
        if (schema == null) {
            LOG.warn("Ignoring invalid schema '{}'", cond.getSchema());
            return EMPTY_ATTR_QUERY;
        }

        UAttrValue attrValue = new UAttrValue();
        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 user_id FROM user_search_attr WHERE ")
                .append("schema_name='").append(schema.getName());
        fillAttributeQuery(query, attrValue, schema, cond, not, parameters);

        return query.toString();
    }

    private String getQuery(final SyncopeUserCond cond, final boolean not, final List<Object> parameters) {

        Field syncopeUserClassField = null;
        // loop over SyncopeUser class and all superclasses searching for field
        for (Class<?> i = SyncopeUser.class; syncopeUserClassField == null && i != Object.class;) {

            try {
                syncopeUserClassField = i.getDeclaredField(cond.getSchema());
            } catch (Exception ignore) {
                // ignore exception
                LOG.debug("Field '{}' not found on class '{}'",
                        new String[] { cond.getSchema(), i.getSimpleName() }, ignore);
            } finally {
                i = i.getSuperclass();
            }
        }
        if (syncopeUserClassField == null) {
            LOG.warn("Ignoring invalid schema '{}'", cond.getSchema());
            return EMPTY_ATTR_QUERY;
        }

        USchema schema = new USchema();
        schema.setName(syncopeUserClassField.getName());
        for (SchemaType type : SchemaType.values()) {
            if (syncopeUserClassField.getType().getName().equals(type.getClassName())) {

                schema.setType(type);
            }
        }

        UAttrValue attrValue = new UAttrValue();
        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 user_id FROM user_search WHERE ");

        fillAttributeQuery(query, attrValue, schema, cond, not, parameters);

        return query.toString();
    }
}