1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.client.console.panels.search;
20
21 import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggle;
22 import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggleConfig;
23 import java.io.Serializable;
24 import java.text.ParseException;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Date;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Optional;
31 import java.util.function.Consumer;
32 import java.util.stream.Collectors;
33 import org.apache.commons.lang3.ArrayUtils;
34 import org.apache.commons.lang3.StringUtils;
35 import org.apache.commons.lang3.time.DateFormatUtils;
36 import org.apache.commons.lang3.time.FastDateFormat;
37 import org.apache.commons.lang3.tuple.Pair;
38 import org.apache.syncope.client.console.panels.search.SearchClause.Comparator;
39 import org.apache.syncope.client.console.panels.search.SearchClause.Operator;
40 import org.apache.syncope.client.console.panels.search.SearchClause.Type;
41 import org.apache.syncope.client.console.rest.GroupRestClient;
42 import org.apache.syncope.client.console.rest.RelationshipTypeRestClient;
43 import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxEventBehavior;
44 import org.apache.syncope.client.lib.SyncopeClient;
45 import org.apache.syncope.client.ui.commons.Constants;
46 import org.apache.syncope.client.ui.commons.SchemaUtils;
47 import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
48 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDateTimeFieldPanel;
49 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
50 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxSpinnerFieldPanel;
51 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
52 import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
53 import org.apache.syncope.common.lib.SyncopeConstants;
54 import org.apache.syncope.common.lib.to.GroupTO;
55 import org.apache.syncope.common.lib.to.PlainSchemaTO;
56 import org.apache.syncope.common.lib.to.RelationshipTypeTO;
57 import org.apache.syncope.common.lib.types.AttrSchemaType;
58 import org.apache.wicket.AttributeModifier;
59 import org.apache.wicket.Component;
60 import org.apache.wicket.ajax.AjaxEventBehavior;
61 import org.apache.wicket.ajax.AjaxRequestTarget;
62 import org.apache.wicket.ajax.attributes.AjaxCallListener;
63 import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
64 import org.apache.wicket.ajax.markup.html.AjaxLink;
65 import org.apache.wicket.event.Broadcast;
66 import org.apache.wicket.event.IEventSink;
67 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
68 import org.apache.wicket.markup.html.WebMarkupContainer;
69 import org.apache.wicket.markup.html.form.CheckBox;
70 import org.apache.wicket.markup.html.form.ChoiceRenderer;
71 import org.apache.wicket.markup.html.form.FormComponent;
72 import org.apache.wicket.markup.html.form.IChoiceRenderer;
73 import org.apache.wicket.markup.html.list.ListItem;
74 import org.apache.wicket.markup.html.panel.Fragment;
75 import org.apache.wicket.model.IModel;
76 import org.apache.wicket.model.LoadableDetachableModel;
77 import org.apache.wicket.model.Model;
78 import org.apache.wicket.model.PropertyModel;
79 import org.apache.wicket.spring.injection.annot.SpringBean;
80
81 public class SearchClausePanel extends FieldPanel<SearchClause> {
82
83 private static final long serialVersionUID = -527351923968737757L;
84
85 protected static final AttributeModifier PREVENT_DEFAULT_RETURN = AttributeModifier.replace(
86 "onkeydown",
87 Model.of("if (event.keyCode == 13) { event.preventDefault(); }"));
88
89 protected static final Consumer<AjaxRequestAttributes> AJAX_SUBMIT_ON_RETURN =
90 attributes -> attributes.getAjaxCallListeners().add(new AjaxCallListener() {
91
92 private static final long serialVersionUID = 7160235486520935153L;
93
94 @Override
95 public CharSequence getPrecondition(final Component component) {
96 return "return (Wicket.Event.keyCode(attrs.event) == 13);";
97 }
98 });
99
100 public interface Customizer extends Serializable {
101
102 default IChoiceRenderer<SearchClause.Type> typeRenderer() {
103 return new ChoiceRenderer<>();
104 }
105
106 default List<Comparator> comparators() {
107 return List.of();
108 }
109
110 default String comparatorDisplayValue(Comparator object) {
111 return object.toString();
112 }
113
114 default Optional<SearchClause.Comparator> comparatorGetObject(String id) {
115 return Optional.empty();
116 }
117
118 default List<String> properties() {
119 return List.of();
120 }
121
122 default void setFieldAccess(
123 FieldPanel<String> value,
124 AjaxTextFieldPanel property,
125 LoadableDetachableModel<List<String>> properties) {
126
127 value.setEnabled(true);
128 value.setModelObject(StringUtils.EMPTY);
129 property.setEnabled(true);
130
131
132 properties.detach();
133 property.setChoices(properties.getObject());
134 }
135 }
136
137 @SpringBean
138 protected RelationshipTypeRestClient relationshipTypeRestClient;
139
140 @SpringBean
141 protected GroupRestClient groupRestClient;
142
143 protected final boolean required;
144
145 protected final IModel<List<SearchClause.Type>> types;
146
147 protected final Customizer customizer;
148
149 protected final IModel<Map<String, PlainSchemaTO>> anames;
150
151 protected final IModel<Map<String, PlainSchemaTO>> dnames;
152
153 protected final Pair<IModel<List<String>>, IModel<Integer>> groupInfo;
154
155 protected final IModel<List<String>> roleNames;
156
157 protected final IModel<List<String>> privilegeNames;
158
159 protected final IModel<List<String>> auxClassNames;
160
161 protected final IModel<List<String>> resourceNames;
162
163 protected IModel<SearchClause> clause;
164
165 protected final LoadableDetachableModel<List<Comparator>> comparators;
166
167 protected final LoadableDetachableModel<List<String>> properties;
168
169 protected final Fragment operatorFragment;
170
171 protected final Fragment searchButtonFragment;
172
173 protected final AjaxLink<Void> searchButton;
174
175 protected IEventSink resultContainer;
176
177 @SuppressWarnings({ "rawtypes", "unchecked" })
178 private FieldPanel value;
179
180 public SearchClausePanel(
181 final String id,
182 final String name,
183 final Model<SearchClause> clause,
184 final boolean required,
185 final IModel<List<SearchClause.Type>> types,
186 final Customizer customizer,
187 final IModel<Map<String, PlainSchemaTO>> anames,
188 final IModel<Map<String, PlainSchemaTO>> dnames,
189 final Pair<IModel<List<String>>, IModel<Integer>> groupInfo,
190 final IModel<List<String>> roleNames,
191 final IModel<List<String>> privilegeNames,
192 final IModel<List<String>> auxClassNames,
193 final IModel<List<String>> resourceNames) {
194
195 super(id, name, clause);
196
197 this.clause = clause == null ? new Model<>(null) : clause;
198
199 this.required = required;
200 this.types = types;
201 this.customizer = customizer;
202 this.anames = anames;
203 this.dnames = dnames;
204 this.groupInfo = groupInfo;
205 this.roleNames = roleNames;
206 this.privilegeNames = privilegeNames;
207 this.auxClassNames = auxClassNames;
208 this.resourceNames = resourceNames;
209
210 searchButton = new AjaxLink<>("search") {
211
212 private static final long serialVersionUID = 5538299138211283825L;
213
214 @Override
215 public void onClick(final AjaxRequestTarget target) {
216 if (resultContainer == null) {
217 send(SearchClausePanel.this, Broadcast.BUBBLE, new SearchEvent(target));
218 } else {
219 send(resultContainer, Broadcast.EXACT, new SearchEvent(target));
220 }
221 }
222 };
223
224 searchButtonFragment = new Fragment("operator", "searchButtonFragment", this);
225 searchButtonFragment.add(searchButton.setEnabled(false).setVisible(false));
226
227 operatorFragment = new Fragment("operator", "operatorFragment", this);
228
229 field = new FormComponent<>("container", this.clause) {
230
231 private static final long serialVersionUID = -8204140666393922700L;
232
233 };
234 add(field);
235
236 comparators = new LoadableDetachableModel<>() {
237
238 private static final long serialVersionUID = 5275935387613157437L;
239
240 @Override
241 protected List<Comparator> load() {
242 if (field.getModel().getObject() == null || field.getModel().getObject().getType() == null) {
243 return List.of();
244 }
245
246 switch (field.getModel().getObject().getType()) {
247 case ATTRIBUTE:
248 return List.of(SearchClause.Comparator.values());
249
250 case AUX_CLASS:
251 case ROLE_MEMBERSHIP:
252 case PRIVILEGE:
253 case GROUP_MEMBERSHIP:
254 case GROUP_MEMBER:
255 case RESOURCE:
256 return List.of(
257 SearchClause.Comparator.EQUALS,
258 SearchClause.Comparator.NOT_EQUALS);
259
260 case RELATIONSHIP:
261 return List.of(
262 SearchClause.Comparator.IS_NOT_NULL,
263 SearchClause.Comparator.IS_NULL,
264 SearchClause.Comparator.EQUALS,
265 SearchClause.Comparator.NOT_EQUALS);
266
267 case CUSTOM:
268 return customizer.comparators();
269
270 default:
271 return List.of();
272 }
273 }
274 };
275
276 properties = new LoadableDetachableModel<>() {
277
278 private static final long serialVersionUID = 5275935387613157437L;
279
280 @Override
281 protected List<String> load() {
282 if (field.getModel().getObject() == null || field.getModel().getObject().getType() == null) {
283 return List.of();
284 }
285
286 switch (field.getModel().getObject().getType()) {
287 case ATTRIBUTE:
288 List<String> names = new ArrayList<>(dnames.getObject().keySet());
289 if (anames != null && anames.getObject() != null && !anames.getObject().isEmpty()) {
290 names.addAll(anames.getObject().keySet());
291 }
292 return names.stream().sorted().collect(Collectors.toList());
293
294 case GROUP_MEMBERSHIP:
295 return groupInfo.getLeft().getObject();
296
297 case ROLE_MEMBERSHIP:
298 return Optional.ofNullable(roleNames).
299 map(r -> r.getObject().stream().sorted().collect(Collectors.toList())).
300 orElse(List.of());
301
302 case PRIVILEGE:
303 return Optional.ofNullable(privilegeNames).
304 map(p -> p.getObject().stream().sorted().collect(Collectors.toList())).
305 orElse(List.of());
306
307 case AUX_CLASS:
308 return auxClassNames.getObject().stream().
309 sorted().collect(Collectors.toList());
310
311 case RESOURCE:
312 return resourceNames.getObject().stream().
313 sorted().collect(Collectors.toList());
314
315 case RELATIONSHIP:
316 return relationshipTypeRestClient.list().stream().
317 map(RelationshipTypeTO::getKey).collect(Collectors.toList());
318
319 case CUSTOM:
320 return customizer.properties();
321
322 default:
323 return List.of();
324 }
325 }
326 };
327 }
328
329 public void enableSearch(final IEventSink resultContainer) {
330 this.resultContainer = resultContainer;
331 this.searchButton.setEnabled(true);
332 this.searchButton.setVisible(true);
333
334 field.add(PREVENT_DEFAULT_RETURN);
335 field.add(new AjaxEventBehavior(Constants.ON_KEYDOWN) {
336
337 private static final long serialVersionUID = -7133385027739964990L;
338
339 @Override
340 protected void onEvent(final AjaxRequestTarget target) {
341 if (resultContainer == null) {
342 send(SearchClausePanel.this, Broadcast.BUBBLE, new SearchEvent(target));
343 } else {
344 send(resultContainer, Broadcast.EXACT, new SearchEvent(target));
345 }
346 }
347
348 @Override
349 protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
350 super.updateAjaxAttributes(attributes);
351 AJAX_SUBMIT_ON_RETURN.accept(attributes);
352 }
353 });
354 }
355
356 @Override
357 public SearchClause getModelObject() {
358 return this.clause.getObject();
359 }
360
361 @Override
362 public FieldPanel<SearchClause> setModelObject(final SearchClause object) {
363 this.clause.setObject(object);
364 return super.setModelObject(object);
365 }
366
367 @Override
368 @SuppressWarnings("rawtypes")
369 public FieldPanel<SearchClause> setNewModel(final ListItem item) {
370 clause.setObject(SearchClause.class.cast(item.getModelObject()));
371 return this;
372 }
373
374 @Override
375 public FieldPanel<SearchClause> setNewModel(final IModel<SearchClause> model) {
376 clause = model;
377 return super.setNewModel(model);
378 }
379
380 @Override
381 @SuppressWarnings({ "rawtypes", "unchecked" })
382 public FieldPanel<SearchClause> settingsDependingComponents() {
383 SearchClause searchClause = this.clause.getObject();
384
385 WebMarkupContainer operatorContainer = new WebMarkupContainer("operatorContainer");
386 operatorContainer.setOutputMarkupId(true);
387 field.add(operatorContainer);
388
389 BootstrapToggleConfig config = new BootstrapToggleConfig().
390 withOnStyle(BootstrapToggleConfig.Style.info).
391 withOffStyle(BootstrapToggleConfig.Style.warning).
392 withSize(BootstrapToggleConfig.Size.mini);
393
394 operatorFragment.add(new BootstrapToggle("operator", new Model<>() {
395
396 private static final long serialVersionUID = -7157802546272668001L;
397
398 @Override
399 public Boolean getObject() {
400 return searchClause.getOperator() == Operator.AND;
401 }
402
403 @Override
404 public void setObject(final Boolean object) {
405 searchClause.setOperator(object ? Operator.AND : Operator.OR);
406 }
407 }, config) {
408
409 private static final long serialVersionUID = 2969634208049189343L;
410
411 @Override
412 protected IModel<String> getOffLabel() {
413 return Model.of("OR");
414 }
415
416 @Override
417 protected IModel<String> getOnLabel() {
418 return Model.of("AND");
419 }
420
421 @Override
422 protected CheckBox newCheckBox(final String id, final IModel<Boolean> model) {
423 CheckBox checkBox = super.newCheckBox(id, model);
424 checkBox.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
425
426 private static final long serialVersionUID = 18266219802290L;
427
428 @Override
429 protected void onUpdate(final AjaxRequestTarget target) {
430 }
431 });
432 return checkBox;
433 }
434 }.setOutputMarkupPlaceholderTag(true));
435
436 if (getIndex() > 0) {
437 operatorContainer.add(operatorFragment);
438 } else {
439 operatorContainer.add(searchButtonFragment);
440 }
441
442 AjaxTextFieldPanel property = new AjaxTextFieldPanel(
443 "property", "property", new PropertyModel<>(searchClause, "property"), true);
444 property.hideLabel().setOutputMarkupId(true).setEnabled(true);
445 property.setChoices(properties.getObject());
446 field.add(property);
447
448 property.getField().add(PREVENT_DEFAULT_RETURN);
449 property.getField().add(new IndicatorAjaxEventBehavior(Constants.ON_KEYUP) {
450
451 private static final long serialVersionUID = -957948639666058749L;
452
453 @Override
454 protected void onEvent(final AjaxRequestTarget target) {
455 if (field.getModel().getObject() != null
456 && field.getModel().getObject().getType() == Type.GROUP_MEMBERSHIP) {
457
458 String[] inputAsArray = property.getField().getInputAsArray();
459 if (ArrayUtils.isEmpty(inputAsArray)) {
460 property.setChoices(properties.getObject());
461 } else if (groupInfo.getRight().getObject() > Constants.MAX_GROUP_LIST_SIZE) {
462 String inputValue = inputAsArray.length > 1 && inputAsArray[1] != null
463 ? inputAsArray[1]
464 : property.getField().getInput();
465 if (!inputValue.startsWith("*")) {
466 inputValue = "*" + inputValue;
467 }
468 if (!inputValue.endsWith("*")) {
469 inputValue += "*";
470 }
471 property.setChoices(groupRestClient.search(
472 SyncopeConstants.ROOT_REALM,
473 SyncopeClient.getGroupSearchConditionBuilder().
474 is(Constants.NAME_FIELD_NAME).equalToIgnoreCase(inputValue).
475 query(),
476 1,
477 Constants.MAX_GROUP_LIST_SIZE,
478 new SortParam<>(Constants.NAME_FIELD_NAME, true),
479 null).stream().map(GroupTO::getName).collect(Collectors.toList()));
480 }
481 }
482 }
483 });
484 property.getField().add(new IndicatorAjaxEventBehavior(Constants.ON_KEYDOWN) {
485
486 private static final long serialVersionUID = -7133385027739964990L;
487
488 @Override
489 protected void onEvent(final AjaxRequestTarget target) {
490 target.focusComponent(null);
491 property.getField().inputChanged();
492 property.getField().validate();
493 if (property.getField().isValid()) {
494 property.getField().valid();
495 property.getField().updateModel();
496 }
497 }
498
499 @Override
500 protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
501 super.updateAjaxAttributes(attributes);
502 AJAX_SUBMIT_ON_RETURN.accept(attributes);
503 }
504 });
505
506 AjaxDropDownChoicePanel<SearchClause.Comparator> comparator = new AjaxDropDownChoicePanel<>(
507 "comparator", "comparator", new PropertyModel<>(searchClause, "comparator"));
508 comparator.setChoices(comparators);
509 comparator.setNullValid(false).hideLabel().setOutputMarkupId(true);
510 comparator.setRequired(required);
511 comparator.setChoiceRenderer(getComparatorRender(field.getModel()));
512 field.add(comparator);
513
514 renderSearchValueField(searchClause, property);
515 field.addOrReplace(value);
516
517 property.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
518
519 private static final long serialVersionUID = -1107858522700306810L;
520
521 @Override
522 protected void onUpdate(final AjaxRequestTarget target) {
523 renderSearchValueField(searchClause, property);
524 field.addOrReplace(value);
525 target.add(value);
526 }
527 });
528
529 AjaxDropDownChoicePanel<SearchClause.Type> type = new AjaxDropDownChoicePanel<>(
530 "type", "type", new PropertyModel<>(searchClause, "type"));
531
532 type.setChoices(types).setChoiceRenderer(customizer.typeRenderer()).
533 hideLabel().setRequired(required).setOutputMarkupId(true);
534 type.setNullValid(false);
535 type.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
536
537 private static final long serialVersionUID = -1107858522700306810L;
538
539 @Override
540 protected void onUpdate(final AjaxRequestTarget target) {
541 final SearchClause searchClause = new SearchClause();
542 if (StringUtils.isNotEmpty(type.getDefaultModelObjectAsString())) {
543 searchClause.setType(Type.valueOf(type.getDefaultModelObjectAsString()));
544 }
545 SearchClausePanel.this.clause.setObject(searchClause);
546
547 setFieldAccess(searchClause.getType(), property, comparator, value);
548
549
550 property.setModelObject(StringUtils.EMPTY);
551 target.add(property);
552
553 target.add(comparator);
554 target.add(value);
555 }
556 });
557 field.add(type);
558
559 comparator.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
560
561 private static final long serialVersionUID = -1107858522700306810L;
562
563 @Override
564 protected void onUpdate(final AjaxRequestTarget target) {
565 if (type.getModelObject() == SearchClause.Type.ATTRIBUTE
566 || type.getModelObject() == SearchClause.Type.RELATIONSHIP) {
567
568 if (comparator.getModelObject() == SearchClause.Comparator.IS_NULL
569 || comparator.getModelObject() == SearchClause.Comparator.IS_NOT_NULL) {
570
571 value.setModelObject(StringUtils.EMPTY);
572 value.setEnabled(false);
573 } else {
574 value.setEnabled(true);
575 }
576 target.add(value);
577 }
578
579 if (type.getModelObject() == SearchClause.Type.RELATIONSHIP) {
580 property.setEnabled(true);
581
582 SearchClause searchClause = new SearchClause();
583 searchClause.setType(Type.valueOf(type.getDefaultModelObjectAsString()));
584 searchClause.setComparator(comparator.getModelObject());
585 SearchClausePanel.this.clause.setObject(searchClause);
586
587 target.add(property);
588 }
589 }
590 });
591
592 setFieldAccess(searchClause.getType(), property, comparator, value);
593
594 return this;
595 }
596
597 @SuppressWarnings({ "rawtypes", "unchecked" })
598 private void setFieldAccess(
599 final Type type,
600 final AjaxTextFieldPanel property,
601 final FieldPanel<Comparator> comparator,
602 final FieldPanel value) {
603
604 if (type != null) {
605 property.setEnabled(true);
606 comparator.setEnabled(true);
607 value.setEnabled(true);
608
609 switch (type) {
610 case ATTRIBUTE:
611 if (!comparator.isEnabled()) {
612 comparator.setEnabled(true);
613 comparator.setRequired(true);
614 }
615
616 if (comparator.getModelObject() == SearchClause.Comparator.IS_NULL
617 || comparator.getModelObject() == SearchClause.Comparator.IS_NOT_NULL) {
618 value.setEnabled(false);
619 value.setModelObject(StringUtils.EMPTY);
620 }
621
622
623 properties.detach();
624 property.setChoices(properties.getObject());
625 break;
626
627 case ROLE_MEMBERSHIP:
628 value.setEnabled(false);
629 value.setModelObject(StringUtils.EMPTY);
630
631
632 properties.detach();
633 property.setChoices(properties.getObject());
634 break;
635
636 case PRIVILEGE:
637 value.setEnabled(false);
638 value.setModelObject(StringUtils.EMPTY);
639
640
641 properties.detach();
642 property.setChoices(properties.getObject());
643 break;
644
645 case GROUP_MEMBERSHIP:
646 value.setEnabled(false);
647 value.setModelObject(StringUtils.EMPTY);
648
649
650 properties.detach();
651 property.setChoices(properties.getObject());
652 break;
653
654 case GROUP_MEMBER:
655 value.setEnabled(true);
656 property.setEnabled(false);
657 property.setModelObject(StringUtils.EMPTY);
658 break;
659
660 case AUX_CLASS:
661 case RESOURCE:
662 value.setEnabled(false);
663 value.setModelObject(StringUtils.EMPTY);
664
665
666 properties.detach();
667 property.setChoices(properties.getObject());
668 break;
669
670 case RELATIONSHIP:
671 value.setEnabled(true);
672 value.setModelObject(StringUtils.EMPTY);
673 property.setEnabled(true);
674
675
676 properties.detach();
677 property.setChoices(properties.getObject());
678 break;
679
680 case CUSTOM:
681 customizer.setFieldAccess(value, property, properties);
682 break;
683
684 default:
685 break;
686 }
687 }
688 }
689
690 private IChoiceRenderer<SearchClause.Comparator> getComparatorRender(final IModel<SearchClause> clause) {
691 return new IChoiceRenderer<>() {
692
693 private static final long serialVersionUID = -9086043750227867686L;
694
695 @Override
696 public Object getDisplayValue(final SearchClause.Comparator object) {
697 if (clause == null || clause.getObject() == null || clause.getObject().getType() == null) {
698 return object.toString();
699 }
700
701 String display;
702
703 switch (clause.getObject().getType()) {
704 case ATTRIBUTE:
705 switch (object) {
706 case IS_NULL:
707 display = "NULL";
708 break;
709
710 case IS_NOT_NULL:
711 display = "NOT NULL";
712 break;
713
714 case EQUALS:
715 display = "==";
716 break;
717
718 case NOT_EQUALS:
719 display = "!=";
720 break;
721
722 case LESS_THAN:
723 display = "<";
724 break;
725
726 case LESS_OR_EQUALS:
727 display = "<=";
728 break;
729
730 case GREATER_THAN:
731 display = ">";
732 break;
733
734 case GREATER_OR_EQUALS:
735 display = ">=";
736 break;
737
738 default:
739 display = StringUtils.EMPTY;
740 }
741 break;
742
743 case GROUP_MEMBERSHIP:
744 switch (object) {
745 case EQUALS:
746 display = "IN";
747 break;
748
749 case NOT_EQUALS:
750 display = "NOT IN";
751 break;
752
753 default:
754 display = StringUtils.EMPTY;
755 }
756 break;
757
758 case GROUP_MEMBER:
759 switch (object) {
760 case EQUALS:
761 display = "WITH";
762 break;
763
764 case NOT_EQUALS:
765 display = "WITHOUT";
766 break;
767
768 default:
769 display = StringUtils.EMPTY;
770 }
771 break;
772
773 case AUX_CLASS:
774 case ROLE_MEMBERSHIP:
775 case PRIVILEGE:
776 case RESOURCE:
777 switch (object) {
778 case EQUALS:
779 display = "HAS";
780 break;
781
782 case NOT_EQUALS:
783 display = "HAS NOT";
784 break;
785
786 default:
787 display = StringUtils.EMPTY;
788 }
789 break;
790
791 case RELATIONSHIP:
792 switch (object) {
793 case IS_NOT_NULL:
794 display = "EXIST";
795 break;
796
797 case IS_NULL:
798 display = "NOT EXIST";
799 break;
800
801 case EQUALS:
802 display = "WITH";
803 break;
804
805 case NOT_EQUALS:
806 display = "WITHOUT";
807 break;
808
809 default:
810 display = StringUtils.EMPTY;
811 }
812 break;
813
814 case CUSTOM:
815 display = customizer.comparatorDisplayValue(object);
816 break;
817
818 default:
819 display = object.toString();
820 }
821 return display;
822 }
823
824 @Override
825 public String getIdValue(final SearchClause.Comparator object, final int index) {
826 return getDisplayValue(object).toString();
827 }
828
829 @Override
830 public SearchClause.Comparator getObject(
831 final String id, final IModel<? extends List<? extends SearchClause.Comparator>> choices) {
832
833 if (id == null) {
834 return SearchClause.Comparator.EQUALS;
835 }
836
837 final SearchClause.Comparator comparator;
838 switch (id) {
839 case "HAS":
840 case "IN":
841 case "WITH":
842 comparator = SearchClause.Comparator.EQUALS;
843 break;
844
845 case "HAS NOT":
846 case "NOT IN":
847 case "WITHOUT":
848 comparator = SearchClause.Comparator.NOT_EQUALS;
849 break;
850
851 case "NULL":
852 case "NOT EXIST":
853 comparator = SearchClause.Comparator.IS_NULL;
854 break;
855
856 case "NOT NULL":
857 case "EXIST":
858 comparator = SearchClause.Comparator.IS_NOT_NULL;
859 break;
860
861 case "==":
862 comparator = SearchClause.Comparator.EQUALS;
863 break;
864
865 case "!=":
866 comparator = SearchClause.Comparator.NOT_EQUALS;
867 break;
868
869 case "<":
870 comparator = SearchClause.Comparator.LESS_THAN;
871 break;
872
873 case "<=":
874 comparator = SearchClause.Comparator.LESS_OR_EQUALS;
875 break;
876
877 case ">":
878 comparator = SearchClause.Comparator.GREATER_THAN;
879 break;
880
881 case ">=":
882 comparator = SearchClause.Comparator.GREATER_OR_EQUALS;
883 break;
884
885 default:
886
887 comparator = customizer.comparatorGetObject(id).orElse(SearchClause.Comparator.EQUALS);
888 break;
889 }
890
891 return comparator;
892 }
893 };
894 }
895
896 @SuppressWarnings({ "rawtypes", "unchecked" })
897 private void renderSearchValueField(
898 final SearchClause searchClause,
899 final AjaxTextFieldPanel property) {
900
901 PlainSchemaTO plainSchema = anames.getObject().get(property.getModelObject());
902 if (plainSchema == null) {
903 PlainSchemaTO defaultPlainTO = new PlainSchemaTO();
904 defaultPlainTO.setType(AttrSchemaType.String);
905 plainSchema = property.getModelObject() == null ? defaultPlainTO
906 : dnames.getObject().getOrDefault(property.getModelObject(), defaultPlainTO);
907 }
908
909 switch (plainSchema.getType()) {
910 case Boolean:
911 value = new AjaxTextFieldPanel(
912 "value",
913 "value",
914 new PropertyModel<>(searchClause, "value"),
915 true);
916 ((AjaxTextFieldPanel) value).setChoices(Arrays.asList("true", "false"));
917
918 break;
919
920 case Date:
921 FastDateFormat fdf = plainSchema.getConversionPattern() == null
922 ? DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT
923 : FastDateFormat.getInstance(plainSchema.getConversionPattern());
924
925 value = new AjaxDateTimeFieldPanel(
926 "value",
927 "value",
928 new PropertyModel(searchClause, "value") {
929
930 private static final long serialVersionUID = 1177692285167186690L;
931
932 @Override
933 public Object getObject() {
934 String date = (String) super.getObject();
935 try {
936 return date != null ? fdf.parse(date) : null;
937 } catch (ParseException ex) {
938 LOG.error("Date parse error {}", date, ex);
939 }
940 return null;
941 }
942
943 @Override
944 public void setObject(final Object object) {
945 if (object instanceof Date) {
946 String valueDate = fdf.format(object);
947 super.setObject(valueDate);
948 } else {
949 super.setObject(object);
950 }
951 }
952 }, DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT);
953 break;
954
955 case Enum:
956 value = new AjaxDropDownChoicePanel<>(
957 "value",
958 "value",
959 new PropertyModel(searchClause, "value"),
960 true);
961 ((AjaxDropDownChoicePanel<String>) value).setChoices(SchemaUtils.getEnumeratedValues(plainSchema));
962
963 if (StringUtils.isNotBlank(plainSchema.getEnumerationKeys())) {
964 Map<String, String> valueMap = SchemaUtils.getEnumeratedKeyValues(plainSchema);
965 ((AjaxDropDownChoicePanel) value).setChoiceRenderer(new IChoiceRenderer<String>() {
966
967 private static final long serialVersionUID = -3724971416312135885L;
968
969 @Override
970 public String getDisplayValue(final String value) {
971 return valueMap.get(value) == null ? value : valueMap.get(value);
972 }
973
974 @Override
975 public String getIdValue(final String value, final int i) {
976 return value;
977 }
978
979 @Override
980 public String getObject(
981 final String id, final IModel<? extends List<? extends String>> choices) {
982 return id;
983 }
984 });
985 }
986 break;
987
988 case Long:
989 value = new AjaxSpinnerFieldPanel.Builder<Long>().enableOnChange().build(
990 "value",
991 "Value",
992 Long.class,
993 new PropertyModel(searchClause, "value"));
994
995 value.add(new AttributeModifier("class", "field value search-spinner"));
996 break;
997
998 case Double:
999 value = new AjaxSpinnerFieldPanel.Builder<Double>().enableOnChange().step(0.1).build(
1000 "value",
1001 "value",
1002 Double.class,
1003 new PropertyModel(searchClause, "value"));
1004 value.add(new AttributeModifier("class", "field value search-spinner"));
1005 break;
1006
1007 default:
1008 value = new AjaxTextFieldPanel(
1009 "value", "value", new PropertyModel<>(searchClause, "value"), true);
1010 break;
1011 }
1012
1013 value.hideLabel().setOutputMarkupId(true);
1014 value.getField().add(PREVENT_DEFAULT_RETURN);
1015 value.getField().add(new IndicatorAjaxEventBehavior(Constants.ON_KEYDOWN) {
1016
1017 private static final long serialVersionUID = -7133385027739964990L;
1018
1019 @Override
1020 protected void onEvent(final AjaxRequestTarget target) {
1021 target.focusComponent(null);
1022 value.getField().inputChanged();
1023 value.getField().validate();
1024 if (value.getField().isValid()) {
1025 value.getField().valid();
1026 value.getField().updateModel();
1027 }
1028 }
1029
1030 @Override
1031 protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
1032 super.updateAjaxAttributes(attributes);
1033 AJAX_SUBMIT_ON_RETURN.accept(attributes);
1034 }
1035 });
1036 }
1037
1038 @Override
1039 public FieldPanel<SearchClause> clone() {
1040 SearchClausePanel panel = new SearchClausePanel(
1041 getId(), name, null, required, types, customizer, anames, dnames, groupInfo,
1042 roleNames, privilegeNames, auxClassNames, resourceNames);
1043 panel.setReadOnly(this.isReadOnly());
1044 panel.setRequired(this.isRequired());
1045 if (searchButton.isEnabled()) {
1046 panel.enableSearch(resultContainer);
1047 }
1048 return panel;
1049 }
1050
1051 public static class SearchEvent implements Serializable {
1052
1053 private static final long serialVersionUID = 2693338614198749301L;
1054
1055 private final AjaxRequestTarget target;
1056
1057 public SearchEvent(final AjaxRequestTarget target) {
1058 this.target = target;
1059 }
1060
1061 public AjaxRequestTarget getTarget() {
1062 return target;
1063 }
1064 }
1065 }