001/*
002 * (C) Copyright 2017-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *    http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Kevin Leturc
018 *     Florent Guillaume
019 */
020package org.nuxeo.ecm.core.query.sql.model;
021
022import java.util.ArrayList;
023import java.util.List;
024import java.util.stream.Collectors;
025import java.util.stream.Stream;
026
027import org.apache.commons.lang3.builder.ToStringBuilder;
028
029/**
030 * Query builder for a query, including ordering, limit and offset.
031 *
032 * @since 10.3
033 */
034public class QueryBuilder {
035
036    protected MultiExpression filter;
037
038    protected OrderByList orders;
039
040    protected long offset;
041
042    protected long limit;
043
044    protected boolean countTotal;
045
046    public QueryBuilder() {
047        filter = new MultiExpression(Operator.AND, new ArrayList<>());
048        orders = new OrderByList();
049    }
050
051    /** Copy constructor. */
052    public QueryBuilder(QueryBuilder other) {
053        filter = new MultiExpression(other.filter);
054        orders = new OrderByList(other.orders);
055        offset = other.offset;
056        limit = other.limit;
057        countTotal = other.countTotal;
058    }
059
060    public MultiExpression predicate() {
061        return filter;
062    }
063
064    /**
065     * Adds a new predicate to the list of AND predicates.
066     */
067    public QueryBuilder and(Predicate predicate) {
068        if (filter.predicates.isEmpty()) {
069            throw new IllegalStateException("Cannot AND without a first predicate");
070        }
071        if (filter.operator != Operator.AND) {
072            if (filter.predicates.size() == 1) {
073                filter = new MultiExpression(Operator.AND, filter.predicates);
074            } else {
075                throw new IllegalStateException("Not an AND MultiExpression");
076            }
077        }
078        return predicate(predicate);
079    }
080
081    /**
082     * Adds a new predicate to the list of OR predicates.
083     */
084    public QueryBuilder or(Predicate predicate) {
085        if (filter.predicates.isEmpty()) {
086            throw new IllegalStateException("Cannot OR without a first predicate");
087        }
088        if (filter.operator != Operator.OR) {
089            if (filter.predicates.size() == 1) {
090                filter = new MultiExpression(Operator.OR, filter.predicates);
091            } else {
092                throw new IllegalStateException("Not an OR MultiExpression");
093            }
094        }
095        return predicate(predicate);
096    }
097
098    /**
099     * Adds a new predicate to the list.
100     */
101    public QueryBuilder predicate(Predicate predicate) {
102        filter.predicates.add(predicate);
103        return this;
104    }
105
106    /**
107     * Sets the filter.
108     */
109    public QueryBuilder filter(MultiExpression filter) {
110        this.filter = new MultiExpression(filter);
111        return this;
112    }
113
114    /**
115     * We currently only need to handle object instantiated through {@link OrderByExprs}.
116     */
117    public OrderByList orders() {
118        return orders;
119    }
120
121    public QueryBuilder defaultOrder() {
122        return this;
123    }
124
125    /**
126     * Adds a new order to this query builder.
127     */
128    public QueryBuilder order(OrderByExpr order) {
129        this.orders.add(order);
130        return this;
131    }
132
133    /**
134     * Sets the orders to use when querying audit.
135     */
136    public QueryBuilder orders(OrderByExpr order, OrderByExpr... orders) {
137        return orders(Stream.concat(Stream.of(order), Stream.of(orders)).collect(Collectors.toList()));
138    }
139
140    /**
141     * Sets the orders to use when querying audit.
142     */
143    public QueryBuilder orders(List<OrderByExpr> orders) {
144        this.orders.clear();
145        this.orders.addAll(orders);
146        return this;
147    }
148
149    public long offset() {
150        return offset;
151    }
152
153    public QueryBuilder offset(long offset) {
154        this.offset = offset;
155        return this;
156    }
157
158    public long limit() {
159        return limit;
160    }
161
162    public QueryBuilder limit(long limit) {
163        this.limit = limit;
164        return this;
165    }
166
167    /**
168     * May be used by <b>supported APIs</b> to include in the query result a count of total results if there was no
169     * limit or offset.
170     * <p>
171     * If {@code true}, requests computation of the total size of the underlying list (the size if there was no limit or
172     * offset), otherwise when {@code false} does a best effort but may return {@code -2} when unknown
173     */
174    public boolean countTotal() {
175        return countTotal;
176    }
177
178    public QueryBuilder countTotal(boolean countTotal) {
179        this.countTotal = countTotal;
180        return this;
181    }
182
183    @Override
184    public String toString() {
185        return ToStringBuilder.reflectionToString(this);
186    }
187
188}