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
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
89
90
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
102
103
104
105 @QueryParam("offset")
106 @DefaultValue("0")
107 public void setOffset(long offset) {
108 this.offset = offset;
109 }
110
111
112
113
114
115
116
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
127
128 @Override
129 public int getPageNumber() {
130 return (int) (limit > 0 ? offset / limit : 0);
131 }
132
133
134
135
136 @Override
137 public int getPageSize() {
138 return limit;
139 }
140
141
142
143
144 @Override
145 public long getOffset() {
146 return offset;
147 }
148
149
150
151
152 @Override
153 public Sort getSort() {
154 return sort;
155 }
156
157
158
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
171
172 @Override
173 public OffsetPageRequest previousOrFirst() {
174 return hasPrevious() ? previous() : first();
175 }
176
177
178
179
180 @Override
181 public OffsetPageRequest first() {
182 return new OffsetPageRequest(limit, 0, sort);
183 }
184
185
186
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
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 }