All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bonitasoft.engine.persistence.QueryBuilder Maven / Gradle / Ivy

There is a newer version: 10.2.0
Show newest version
/**
 * Copyright (C) 2019 Bonitasoft S.A.
 * Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation
 * version 2.1 of the License.
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301, USA.
 **/
package org.bonitasoft.engine.persistence;

import static java.util.Collections.emptySet;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import lombok.extern.slf4j.Slf4j;
import org.hibernate.Session;
import org.hibernate.query.Query;

/**
 * @author Baptiste Mesta
 */
@Slf4j
abstract class QueryBuilder {

    private final Query baseQuery;
    private final boolean wordSearchEnabled;
    private final OrderByCheckingMode orderByCheckingMode;
    private final AbstractSelectDescriptor selectDescriptor;
    private final QueryGeneratorForFilters queryGeneratorForFilters;
    private final QueryGeneratorForSearchTerm queryGeneratorForSearchTerm;
    private final QueryGeneratorForOrderBy queryGeneratorForOrderBy;
    StringBuilder stringQueryBuilder;
    private Map classAliasMappings;
    private Session session;
    private long tenantId;
    private boolean cacheEnabled;
    private Map parameters = new HashMap<>();

    QueryBuilder(Session session, Query baseQuery, OrderByBuilder orderByBuilder,
            Map classAliasMappings,
            char likeEscapeCharacter, boolean wordSearchEnabled, OrderByCheckingMode orderByCheckingMode,
            AbstractSelectDescriptor selectDescriptor) {
        this.session = session;
        this.classAliasMappings = classAliasMappings;
        stringQueryBuilder = new StringBuilder(baseQuery.getQueryString());
        this.baseQuery = baseQuery;
        this.wordSearchEnabled = wordSearchEnabled;
        this.orderByCheckingMode = orderByCheckingMode;
        this.selectDescriptor = selectDescriptor;
        this.queryGeneratorForFilters = new QueryGeneratorForFilters(classAliasMappings,
                likeEscapeCharacter);
        this.queryGeneratorForSearchTerm = new QueryGeneratorForSearchTerm(likeEscapeCharacter);
        this.queryGeneratorForOrderBy = new QueryGeneratorForOrderBy(classAliasMappings, orderByBuilder);

    }

    public String getQuery() {
        return stringQueryBuilder.toString();
    }

    void appendFilters(List filters, SearchFields multipleFilter, boolean enableWordSearch) {
        Set specificFilters = emptySet();
        if (!filters.isEmpty()) {
            if (!hasWHEREInRootQuery(stringQueryBuilder.toString())) {
                stringQueryBuilder.append(" WHERE (");
            } else {
                stringQueryBuilder.append(" AND (");
            }
            QueryGeneratorForFilters.QueryGeneratedFilters whereClause = queryGeneratorForFilters.generate(filters);
            specificFilters = whereClause.getSpecificFilters();
            stringQueryBuilder.append(whereClause.getFilters());
            stringQueryBuilder.append(")");
            parameters.putAll(whereClause.getParameters());
        }
        if (multipleFilter != null && multipleFilter.getTerms() != null && !multipleFilter.getTerms().isEmpty()) {
            handleMultipleFilters(stringQueryBuilder, multipleFilter, specificFilters, enableWordSearch);
        }
    }

    static boolean hasWHEREInRootQuery(String query) {
        // We simply remove all blocks that are in parenthesis in order to remove all subqueries
        // Then we check if there is the `where` word here
        return removeAllParenthesisBlocks(query.toLowerCase()).contains("where");
    }

    private static String removeAllParenthesisBlocks(String q) {
        StringBuilder stringBuilder = new StringBuilder(q.length());
        int depthCounter = 0;
        for (char c : q.toCharArray()) {
            switch (c) {
                case '(':
                    depthCounter++;
                    break;
                case ')':
                    depthCounter--;
                    break;
                default:
                    if (depthCounter == 0) {
                        stringBuilder.append(c);
                    }
            }
        }
        return stringBuilder.toString();
    }

    private void handleMultipleFilters(final StringBuilder builder, final SearchFields multipleFilter,
            final Set specificFilters,
            final boolean enableWordSearch) {
        final Map, Set> allTextFields = multipleFilter.getFields();
        final Set fields = new HashSet<>();
        for (final Map.Entry, Set> entry : allTextFields.entrySet()) {
            final String alias = classAliasMappings.get(entry.getKey().getName());
            for (final String field : entry.getValue()) {
                fields.add(alias + '.' + field);
            }
        }
        fields.removeAll(specificFilters);

        if (!fields.isEmpty()) {
            final List terms = multipleFilter.getTerms();
            applyFiltersOnQuery(builder, fields, terms, enableWordSearch);
        }
    }

    private void applyFiltersOnQuery(final StringBuilder queryBuilder, final Set fields,
            final List terms, final boolean enableWordSearch) {
        if (!hasWHEREInRootQuery(queryBuilder.toString())) {
            queryBuilder.append(" WHERE ");
        } else {
            queryBuilder.append(" AND ");
        }
        queryBuilder.append("(");

        QueryGeneratorForSearchTerm.QueryGeneratedSearchTerms result = queryGeneratorForSearchTerm.generate(fields,
                terms, enableWordSearch);
        queryBuilder.append(result.getSearch());

        queryBuilder.append(")");
        parameters.putAll(result.getParameters());
    }

    void appendOrderByClause(List orderByOptions, Class entityType)
            throws SBonitaReadException {
        String result = queryGeneratorForOrderBy.generate(orderByOptions, entityType);
        stringQueryBuilder.append(result);
    }

    boolean hasChanged() {
        return !baseQuery.getQueryString().equals(stringQueryBuilder.toString());
    }

    public abstract void setTenantId(Query query, long tenantId);

    abstract Query rebuildQuery(AbstractSelectDescriptor selectDescriptor, Session session, Query query);

    void manageFiltersAndParameters(AbstractSelectDescriptor selectDescriptor, boolean enableWordSearch)
            throws SBonitaReadException {
        if (selectDescriptor.hasAFilter()) {
            final QueryOptions queryOptions = selectDescriptor.getQueryOptions();
            appendFilters(queryOptions.getFilters(), queryOptions.getMultipleFilter(),
                    enableWordSearch);
        }
        if (selectDescriptor.hasOrderByParameters()) {
            appendOrderByClause(selectDescriptor.getQueryOptions().getOrderByOptions(),
                    selectDescriptor.getEntityType());
        }
    }

    public QueryBuilder tenantId(long tenantId) {
        this.tenantId = tenantId;
        return this;
    }

    public QueryBuilder cache(boolean cacheEnabled) {
        this.cacheEnabled = cacheEnabled;
        return this;
    }

    private void setParameters(final Query query, final Map inputParameters) {
        for (final Map.Entry entry : inputParameters.entrySet()) {
            final Object value = entry.getValue();
            if (value instanceof Collection) {
                query.setParameterList(entry.getKey(), (Collection) value);
            } else {
                query.setParameter(entry.getKey(), value);
            }
        }
    }

    public Query build() throws SBonitaReadException {
        manageFiltersAndParameters(selectDescriptor, wordSearchEnabled);
        Query query = baseQuery;
        if (hasChanged()) {
            query = rebuildQuery(selectDescriptor, session, baseQuery);
        }
        addConstantsAsParameters(query);
        setTenantId(query, tenantId);
        setParameters(query, selectDescriptor.getInputParameters());
        query.setFirstResult(selectDescriptor.getStartIndex());
        query.setMaxResults(selectDescriptor.getPageSize());
        query.setCacheable(cacheEnabled);
        checkOrderByClause(query);
        return query;
    }

    protected abstract void addConstantsAsParameters(Query query);

    private void checkOrderByClause(final Query query) {
        if (!query.getQueryString().toLowerCase().contains("order by")) {
            switch (orderByCheckingMode) {
                case NONE:
                    break;
                case WARNING:
                    log.warn(
                            "Query '{}' does not contain 'ORDER BY' clause. It's better to modify your query to order" +
                                    " the result, especially if you use the pagination.",
                            query.getQueryString());
                    break;
                case STRICT:
                default:
                    throw new IllegalArgumentException("Query " + query.getQueryString()
                            + " does not contain 'ORDER BY' clause hence is not allowed. Please specify ordering before re-sending the query");
            }
        }
    }

    /*
     * escape for other things than like
     */
    static String escapeString(final String term) {
        // 1) escape ' character by adding another ' character
        return term
                .replaceAll("'", "''");
    }

    /*
     * escape for like
     */
    static String escapeTerm(final String term, String likeEscapeCharacter) {
        // 1) protect escape character if this character is used in data
        // 2) escape % character (sql query wildcard) by adding escape character
        // 3) escape _ character (sql query wildcard) by adding escape character
        return term
                .replace(likeEscapeCharacter, likeEscapeCharacter + likeEscapeCharacter)
                .replace("%", likeEscapeCharacter + "%")
                .replace("_", likeEscapeCharacter + "_");
    }

    Map getQueryParameters() {
        return parameters;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy