1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.primefaces.extensions.model.mongo;
23
24 import java.beans.PropertyDescriptor;
25 import java.io.Serializable;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Objects;
32 import java.util.function.BiConsumer;
33 import java.util.function.Consumer;
34 import java.util.logging.Logger;
35
36 import javax.faces.context.FacesContext;
37 import javax.faces.convert.Converter;
38
39 import org.primefaces.context.PrimeApplicationContext;
40 import org.primefaces.model.FilterMeta;
41 import org.primefaces.model.LazyDataModel;
42 import org.primefaces.model.SortMeta;
43 import org.primefaces.model.SortOrder;
44 import org.primefaces.util.Callbacks;
45 import org.primefaces.util.ComponentUtils;
46 import org.primefaces.util.Constants;
47 import org.primefaces.util.PropertyDescriptorResolver;
48
49 import dev.morphia.Datastore;
50 import dev.morphia.query.CountOptions;
51 import dev.morphia.query.FindOptions;
52 import dev.morphia.query.MorphiaCursor;
53 import dev.morphia.query.Query;
54 import dev.morphia.query.Sort;
55 import dev.morphia.query.filters.Filters;
56 import dev.morphia.query.filters.RegexFilter;
57
58
59
60
61
62
63 public class MorphiaLazyDataModel<T> extends LazyDataModel<T> implements Serializable {
64
65 private static final Logger LOGGER = Logger.getLogger(MorphiaLazyDataModel.class.getName());
66
67 protected Class<T> entityClass;
68 protected Callbacks.SerializableSupplier<Datastore> datastore;
69 protected String rowKeyField;
70 protected Callbacks.SerializableFunction<T, Object> rowKeyProvider;
71
72
73
74
75
76 private final Map<String, BiConsumer<Query<T>, FilterMeta>> overrides = new HashMap<>();
77
78 private transient Consumer<Query<T>> prependConsumer;
79
80 private transient BiConsumer<Query<T>, FilterMeta> globalFilterConsumer;
81
82 private transient Callbacks.SerializableSupplier<FindOptions> findOptionsSupplier;
83
84 private transient Callbacks.SerializableSupplier<CountOptions> countOptionsSupplier;
85
86
87
88
89 public MorphiaLazyDataModel() {
90
91 }
92
93 @Override
94 public T getRowData(final String rowKey) {
95 List<T> values = Objects.requireNonNullElseGet(getWrappedData(), Collections::emptyList);
96 for (T obj : values) {
97 if (Objects.equals(rowKey, getRowKey(obj))) {
98 return obj;
99 }
100 }
101
102 return null;
103 }
104
105 @Override
106 public String getRowKey(final T object) {
107 return String.valueOf(rowKeyProvider.apply(object));
108 }
109
110 @Override
111 public int count(final Map<String, FilterMeta> map) {
112 final Query<T> q = this.buildQuery();
113 final CountOptions opts = getCountOptions();
114 final long count = applyFilters(q, map).count(opts);
115 return (int) count;
116 }
117
118 @Override
119 public List<T> load(final int first, final int pageSize, final Map<String, SortMeta> sort,
120 final Map<String, FilterMeta> filters) {
121 final Query<T> q = buildQuery();
122 final FindOptions opt = getFindOptions();
123 sort.forEach((field, sortData) -> opt.sort(sortData.getOrder() == SortOrder.DESCENDING ? Sort.descending(field) : Sort.ascending(field)));
124
125 applyFilters(q, filters);
126 opt.skip(first).limit(pageSize);
127 try (MorphiaCursor<T> cursor = q.iterator(opt)) {
128 return cursor.toList();
129 }
130 }
131
132 protected FindOptions getFindOptions() {
133 try {
134 return findOptionsSupplier != null ? findOptionsSupplier.get() : new FindOptions();
135 }
136 catch (Exception e) {
137
138 return new FindOptions();
139 }
140 }
141
142 protected CountOptions getCountOptions() {
143 try {
144 return countOptionsSupplier != null ? countOptionsSupplier.get() : new CountOptions();
145 }
146 catch (Exception e) {
147
148 return new CountOptions();
149 }
150 }
151
152 public Query<T> applyFilters(final Query<T> q, final Map<String, FilterMeta> filters) {
153 PrimeApplicationContext primeAppContext = PrimeApplicationContext.getCurrentInstance(FacesContext.getCurrentInstance());
154
155 filters.forEach((field, metadata) -> {
156
157 if (metadata.getFilterValue() != null) {
158 final BiConsumer<Query<T>, FilterMeta> override = overrides.get(field);
159 if (override != null) {
160 override.accept(q, metadata);
161 }
162 else {
163 final Object val = metadata.getFilterValue();
164 if (metadata.getMatchMode() != null) {
165
166 switch (metadata.getMatchMode()) {
167 case STARTS_WITH:
168 final RegexFilter regStartsWith = Filters.regex(field, "^" + val).caseInsensitive();
169 q.filter(regStartsWith);
170 break;
171 case ENDS_WITH:
172 final RegexFilter regEndsWith = Filters.regex(field, val + "$").caseInsensitive();
173 q.filter(regEndsWith);
174 break;
175 case CONTAINS:
176 q.filter(Filters.regex(field, val + "").caseInsensitive());
177 break;
178 case EXACT:
179 final Object castedValueEx = convertToDataType(primeAppContext, field, val);
180 if (castedValueEx != null) {
181 q.filter(Filters.eq(field, castedValueEx));
182 }
183 else {
184 q.filter(Filters.eq(field, val));
185 }
186 break;
187 case LESS_THAN:
188 final Object castedValueLt = convertToDataType(primeAppContext, field, val);
189 if (castedValueLt != null) {
190 q.filter(Filters.lt(field, castedValueLt));
191 }
192 else {
193 q.filter(Filters.lt(field, val));
194 }
195 break;
196 case LESS_THAN_EQUALS:
197 final Object castedValueLte = convertToDataType(primeAppContext, field, val);
198 if (castedValueLte != null) {
199 q.filter(Filters.lte(field, castedValueLte));
200 }
201 else {
202 q.filter(Filters.lte(field, val));
203 }
204 break;
205 case GREATER_THAN:
206 final Object castedValueGt = convertToDataType(primeAppContext, field, val);
207 if (castedValueGt != null) {
208 q.filter(Filters.gt(field, castedValueGt));
209 }
210 else {
211 q.filter(Filters.gt(field, val));
212 }
213 break;
214 case GREATER_THAN_EQUALS:
215
216 final Object castedValueGte = convertToDataType(primeAppContext, field, val);
217 if (castedValueGte != null) {
218 q.filter(Filters.gte(field, castedValueGte));
219 }
220 else {
221 q.filter(Filters.gte(field, val));
222 }
223 break;
224 case EQUALS:
225 q.filter(Filters.eq(field, val));
226 break;
227 case IN:
228 if (metadata.getFilterValue().getClass() == Object[].class) {
229 final Object[] parts = (Object[]) metadata.getFilterValue();
230 q.filter(Filters.in(field, Arrays.asList(parts)));
231 }
232 break;
233 case BETWEEN:
234 if (metadata.getFilterValue() instanceof List) {
235 final List<?> dates = (List) metadata.getFilterValue();
236 if (dates.size() > 1) {
237 q.filter(Filters.gte(field, dates.get(0)), Filters.lte(field, dates.get(1)));
238 }
239 }
240 break;
241 case NOT_CONTAINS:
242 q.filter(Filters.regex(field, val + Constants.EMPTY_STRING).caseInsensitive().not());
243 break;
244 case NOT_EQUALS:
245 final Object castedValueNe = convertToDataType(primeAppContext, field, val);
246 if (castedValueNe != null) {
247 q.filter(Filters.eq(field, castedValueNe).not());
248 }
249 else {
250 q.filter(Filters.eq(field, val).not());
251 }
252 break;
253 case NOT_STARTS_WITH:
254 final RegexFilter regStartsWithNot = Filters.regex(field, "^" + val).caseInsensitive();
255 q.filter(regStartsWithNot.not());
256 break;
257 case NOT_IN:
258 if (metadata.getFilterValue() instanceof Object[]) {
259 final Object[] parts = (Object[]) metadata.getFilterValue();
260 q.filter(Filters.nin(field, Arrays.asList(parts)));
261 }
262 break;
263 case NOT_ENDS_WITH:
264 final RegexFilter regEndsWithNot = Filters.regex(field, val + "$").caseInsensitive();
265 q.filter(regEndsWithNot.not());
266 break;
267 case GLOBAL:
268 if (globalFilterConsumer != null) {
269 globalFilterConsumer.accept(q, metadata);
270 }
271 break;
272
273 default:
274 throw new UnsupportedOperationException("MatchMode " + metadata.getMatchMode() + " not supported");
275 }
276 }
277 }
278 }
279 });
280 return q;
281 }
282
283
284
285
286 @Deprecated
287 public MorphiaLazyDataModel<T> prependQuery(final Consumer<Query<T>> consumer) {
288 this.prependConsumer = consumer;
289 return this;
290 }
291
292
293
294
295 @Deprecated
296 public MorphiaLazyDataModel<T> findOptions(final Callbacks.SerializableSupplier<FindOptions> supplier) {
297 this.findOptionsSupplier = supplier;
298 return this;
299 }
300
301
302
303
304 @Deprecated
305 public MorphiaLazyDataModel<T> countOptions(final Callbacks.SerializableSupplier<CountOptions> supplier) {
306 this.countOptionsSupplier = supplier;
307 return this;
308 }
309
310
311
312
313 @Deprecated
314 public MorphiaLazyDataModel<T> globalFilter(final BiConsumer<Query<T>, FilterMeta> consumer) {
315 this.globalFilterConsumer = consumer;
316 return this;
317 }
318
319
320
321
322 @Deprecated
323 public MorphiaLazyDataModel<T> overrideFieldQuery(final String field, final BiConsumer<Query<T>, FilterMeta> consumer) {
324 this.overrides.put(field, consumer);
325 return this;
326 }
327
328 private Object convertToDataType(PrimeApplicationContext primeAppContext, final String field, final Object value) {
329 PropertyDescriptorResolver propResolver = primeAppContext.getPropertyDescriptorResolver();
330 final PropertyDescriptor propertyDescriptor = propResolver.get(entityClass, field);
331 return ComponentUtils.convertToType(value, propertyDescriptor.getPropertyType(), LOGGER);
332 }
333
334 private Query<T> buildQuery() {
335 final Query<T> q = datastore.get().find(entityClass).disableValidation();
336 if (prependConsumer != null) {
337 prependConsumer.accept(q);
338 }
339 return q;
340 }
341
342 public static <T> Builder<T> builder() {
343 return new Builder<>();
344 }
345
346 public static class Builder<T> {
347 private final MorphiaLazyDataModel<T> model;
348
349 public Builder() {
350 model = new MorphiaLazyDataModel<>();
351 }
352
353 public Builder<T> entityClass(Class<T> entityClass) {
354 model.entityClass = entityClass;
355 return this;
356 }
357
358 public Builder<T> datastore(Callbacks.SerializableSupplier<Datastore> datastore) {
359 model.datastore = datastore;
360 return this;
361 }
362
363 public Builder<T> rowKeyConverter(Converter<T> rowKeyConverter) {
364 model.rowKeyConverter = rowKeyConverter;
365 return this;
366 }
367
368 public Builder<T> rowKeyProvider(Callbacks.SerializableFunction<T, Object> rowKeyProvider) {
369 model.rowKeyProvider = rowKeyProvider;
370 return this;
371 }
372
373 public Builder<T> rowKeyField(String rowKey) {
374 model.rowKeyField = rowKey;
375 return this;
376 }
377
378 public Builder<T> findOptions(Callbacks.SerializableSupplier<FindOptions> findOptionsSupplier) {
379 model.findOptionsSupplier = findOptionsSupplier;
380 return this;
381 }
382
383 public Builder<T> countOptions(Callbacks.SerializableSupplier<CountOptions> countOptionsSupplier) {
384 model.countOptionsSupplier = countOptionsSupplier;
385 return this;
386 }
387
388 public Builder<T> prependQuery(final Consumer<Query<T>> consumer) {
389 model.prependConsumer = consumer;
390 return this;
391 }
392
393 public Builder<T> globalFilter(final BiConsumer<Query<T>, FilterMeta> consumer) {
394 model.globalFilterConsumer = consumer;
395 return this;
396 }
397
398 public Builder<T> overrideFieldQuery(final String field, final BiConsumer<Query<T>, FilterMeta> consumer) {
399 model.overrides.put(field, consumer);
400 return this;
401 }
402
403 public MorphiaLazyDataModel<T> build() {
404 Objects.requireNonNull(model.entityClass, "entityClass not set");
405 Objects.requireNonNull(model.datastore, "datastore not set");
406
407 boolean requiresRowKeyProvider = model.rowKeyProvider == null && (model.rowKeyConverter != null || model.rowKeyField != null);
408 if (requiresRowKeyProvider) {
409 if (model.rowKeyConverter != null) {
410 model.rowKeyProvider = model::getRowKeyFromConverter;
411 }
412 else {
413 Objects.requireNonNull(model.rowKeyField, "rowKeyField is mandatory if neither rowKeyProvider nor converter is provided");
414
415 PropertyDescriptorResolver propResolver = PrimeApplicationContext.getCurrentInstance(FacesContext.getCurrentInstance())
416 .getPropertyDescriptorResolver();
417 model.rowKeyProvider = obj -> propResolver.getValue(obj, model.rowKeyField);
418 }
419 }
420 return model;
421 }
422 }
423 }