View Javadoc
1   package org.oxerr.commons.ws.rs.data;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.List;
6   import java.util.Optional;
7   import java.util.stream.Collectors;
8   
9   import javax.ws.rs.DefaultValue;
10  import javax.ws.rs.QueryParam;
11  
12  import org.springframework.data.domain.Pageable;
13  import org.springframework.data.domain.Sort;
14  import org.springframework.data.domain.Sort.Direction;
15  import org.springframework.data.domain.Sort.Order;
16  import org.springframework.util.Assert;
17  import org.springframework.util.StringUtils;
18  
19  /**
20   * {@link Pageable} implementation using limit-offset mode.
21   */
22  public class OffsetPageRequest implements Pageable, Serializable {
23  
24  	public static OffsetPageRequest of() {
25  		return new OffsetPageRequest();
26  	}
27  
28  	public static OffsetPageRequest of(int limit, long offset) {
29  		return new OffsetPageRequest(limit, offset);
30  	}
31  
32  	public static OffsetPageRequest of(int limit, long offset, Sort sort) {
33  		return new OffsetPageRequest(limit, offset, sort);
34  	}
35  
36  	private static final long serialVersionUID = 2017071101L;
37  
38  	private static final int DEFAULT_MAX_LIMIT = 500;
39  
40  	private static final int DEFAULT_LIMIT = 10;
41  
42  	private static final String DEFAULT_LIMIT_STRING = "10";
43  
44  	private static final String DEFAULT_PROPERTY_DELIMITER = ",";
45  
46  	private int maxLimit = DEFAULT_MAX_LIMIT;
47  
48  	private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;
49  
50  	private int limit;
51  
52  	private long offset;
53  
54  	private Sort sort;
55  
56  	public OffsetPageRequest() {
57  		this(DEFAULT_LIMIT, 0, Sort.unsorted());
58  	}
59  
60  	public OffsetPageRequest(int limit, long offset) {
61  		this(limit, offset, Sort.unsorted());
62  	}
63  
64  	public OffsetPageRequest(int limit, long offset, Sort sort) {
65  		Assert.notNull(sort, "Sort must not be null!");
66  
67  		this.check(limit);
68  
69  		this.limit = limit;
70  		this.offset = offset;
71  		this.sort = sort;
72  	}
73  
74  	public OffsetPageRequest defaultSort(Direction direction, String... properties) {
75  		final OffsetPageRequest ret;
76  		final Sort s = getSort();
77  
78  		if (s.isSorted()) {
79  			ret = this;
80  		} else {
81  			ret = new OffsetPageRequest(getPageSize(), getOffset(), Sort.by(direction, properties));
82  		}
83  
84  		return ret;
85  	}
86  
87  	/**
88  	 * Sets limit.
89  	 *
90  	 * @param limit the size of the page to be returned.
91  	 */
92  	@QueryParam("limit")
93  	@DefaultValue(DEFAULT_LIMIT_STRING)
94  	public void setLimit(int limit) {
95  		this.check(limit);
96  
97  		this.limit = limit;
98  	}
99  
100 	/**
101 	 * Sets offset.
102 	 *
103 	 * @param offset number of records to be skipped from the result set.
104 	 */
105 	@QueryParam("offset")
106 	@DefaultValue("0")
107 	public void setOffset(long offset) {
108 		this.offset = offset;
109 	}
110 
111 	/**
112 	 * Sets sorts.
113 	 *
114 	 * @param sorts the sorting strings.
115 	 * One sorting string is a comma separated string of property and direction,
116 	 * such as {@literal property,asc}, {@literal property,desc}.
117 	 */
118 	@QueryParam("sort[]")
119 	public void setSort(List<String> sorts) {
120 		Assert.notNull(sorts, "Sorts must not be null!");
121 
122 		sort = parseSort(sorts);
123 	}
124 
125 	/**
126 	 * {@inheritDoc}
127 	 */
128 	@Override
129 	public int getPageNumber() {
130 		return (int) (limit > 0 ? offset / limit : 0);
131 	}
132 
133 	/**
134 	 * {@inheritDoc}
135 	 */
136 	@Override
137 	public int getPageSize() {
138 		return limit;
139 	}
140 
141 	/**
142 	 * {@inheritDoc}
143 	 */
144 	@Override
145 	public long getOffset() {
146 		return offset;
147 	}
148 
149 	/**
150 	 * {@inheritDoc}
151 	 */
152 	@Override
153 	public Sort getSort() {
154 		return sort;
155 	}
156 
157 	/**
158 	 * {@inheritDoc}
159 	 */
160 	@Override
161 	public OffsetPageRequest next() {
162 		return new OffsetPageRequest(limit, offset + limit, sort);
163 	}
164 
165 	public OffsetPageRequest previous() {
166 		return getOffset() == 0 ? this : new OffsetPageRequest(limit, getOffset() - limit, getSort());
167 	}
168 
169 	/**
170 	 * {@inheritDoc}
171 	 */
172 	@Override
173 	public OffsetPageRequest previousOrFirst() {
174 		return hasPrevious() ? previous() : first();
175 	}
176 
177 	/**
178 	 * {@inheritDoc}
179 	 */
180 	@Override
181 	public OffsetPageRequest first() {
182 		return new OffsetPageRequest(limit, 0, sort);
183 	}
184 
185 	/**
186 	 * {@inheritDoc}
187 	 */
188 	@Override
189 	public OffsetPageRequest withPage(int pageNumber) {
190 		return new OffsetPageRequest(this.limit, (long) this.limit * (long) pageNumber, getSort());
191 	}
192 
193 	/**
194 	 * {@inheritDoc}
195 	 */
196 	@Override
197 	public boolean hasPrevious() {
198 		return offset > 0;
199 	}
200 
201 	protected int getMaxLimit() {
202 		return this.maxLimit;
203 	}
204 
205 	protected String getPropertyDelimiter() {
206 		return this.propertyDelimiter;
207 	}
208 
209 	protected String filterProperty(String property) {
210 		return StringUtils.replace(property, "'", "''");
211 	}
212 
213 	protected Optional<Direction> parseDirection(String value) {
214 		return Direction.fromOptionalString(value);
215 	}
216 
217 	protected List<Order> parseOrders(final List<String> source) {
218 		final List<String> parts = source.stream()
219 			.filter(java.util.Objects::nonNull)
220 			.collect(Collectors.toList());
221 		final String delimiter = this.getPropertyDelimiter();
222 		final List<Order> allOrders = new ArrayList<>();
223 
224 		for (final String part : parts) {
225 			final String[] elements = part.split(delimiter);
226 			final Optional<Direction> direction = elements.length == 0
227 				? Optional.empty()
228 				: this.parseDirection(elements[elements.length - 1]);
229 
230 			for (int i = 0; i < elements.length; i++) {
231 
232 				if (i == elements.length - 1 && direction.isPresent()) {
233 					continue;
234 				}
235 
236 				final String property = this.filterProperty(elements[i]);
237 
238 				if (StringUtils.hasText(property)) {
239 					allOrders.add(new Order(direction.orElse(null), property));
240 				}
241 			}
242 		}
243 
244 		return allOrders;
245 	}
246 
247 	protected Sort parseSort(List<String> sorts) {
248 		final List<Order> orders = parseOrders(sorts);
249 		return Sort.by(orders);
250 	}
251 
252 	private void check(int limit) {
253 		if (limit > this.getMaxLimit()) {
254 			throw new IllegalArgumentException("Limit is exceeded.");
255 		}
256 	}
257 
258 	@Override
259 	public int hashCode() {
260 		final int prime = 31;
261 		int result = 1;
262 		result = prime * result + limit;
263 		result = prime * result + Long.hashCode(offset);
264 		result = prime * result + sort.hashCode();
265 		return result;
266 	}
267 
268 	@Override
269 	public boolean equals(Object obj) {
270 		if (this == obj) {
271 			return true;
272 		}
273 		if (obj == null) {
274 			return false;
275 		}
276 		if (getClass() != obj.getClass()) {
277 			return false;
278 		}
279 		OffsetPageRequest other = (OffsetPageRequest) obj;
280 		if (limit != other.limit) {
281 			return false;
282 		}
283 		if (offset != other.offset) {
284 			return false;
285 		}
286 		return sort.equals(other.sort);
287 	}
288 
289 	@Override
290 	public String toString() {
291 		return String.format("Page request [number: %d, size %d, sort: %s]", getPageNumber(), getPageSize(), sort);
292 	}
293 
294 }