View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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             // reload properties list
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                 // reset property value in case and just in case of change of type
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                     // reload properties list
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                     // reload properties list
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                     // reload properties list
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                     // reload properties list
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                     // reload properties list
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                     // reload properties list
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                         // EQUALS to be used as default value
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 }