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}