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.wizards.any;
20
21 import java.time.Duration;
22 import java.time.temporal.ChronoUnit;
23 import java.util.List;
24 import java.util.Objects;
25 import java.util.Optional;
26 import java.util.concurrent.atomic.AtomicReference;
27 import java.util.stream.Collectors;
28 import org.apache.commons.lang3.StringUtils;
29 import org.apache.commons.lang3.tuple.Pair;
30 import org.apache.syncope.client.console.rest.ResourceRestClient;
31 import org.apache.syncope.client.lib.SyncopeClient;
32 import org.apache.syncope.client.ui.commons.ConnIdSpecialName;
33 import org.apache.syncope.client.ui.commons.Constants;
34 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
35 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
36 import org.apache.syncope.common.lib.to.ConnObject;
37 import org.apache.syncope.common.lib.to.LinkedAccountTO;
38 import org.apache.syncope.common.lib.to.ResourceTO;
39 import org.apache.syncope.common.lib.types.AnyTypeKind;
40 import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery;
41 import org.apache.wicket.Component;
42 import org.apache.wicket.ajax.AjaxEventBehavior;
43 import org.apache.wicket.ajax.AjaxRequestTarget;
44 import org.apache.wicket.ajax.attributes.AjaxCallListener;
45 import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
46 import org.apache.wicket.ajax.attributes.ThrottlingSettings;
47 import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
48 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
49 import org.apache.wicket.extensions.wizard.WizardStep;
50 import org.apache.wicket.model.PropertyModel;
51 import org.apache.wicket.spring.injection.annot.SpringBean;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 public class LinkedAccountDetailsPanel extends WizardStep {
56
57 private static final long serialVersionUID = 1221037007528732347L;
58
59 @SpringBean
60 protected ResourceRestClient resourceRestClient;
61
62 protected static final Logger LOG = LoggerFactory.getLogger(LinkedAccountDetailsPanel.class);
63
64 protected static final int SEARCH_SIZE = 20;
65
66 protected List<String> connObjectKeyFieldValues;
67
68 public LinkedAccountDetailsPanel(final LinkedAccountTO linkedAccountTO) {
69 super();
70 setOutputMarkupId(true);
71
72 AjaxDropDownChoicePanel<String> dropdownResourceField = new AjaxDropDownChoicePanel<>(
73 "resource",
74 "resource",
75 new PropertyModel<>(linkedAccountTO, "resource"),
76 false);
77
78 dropdownResourceField.setChoices(resourceRestClient.list().stream().
79 filter(resource -> resource.getProvision(AnyTypeKind.USER.name()).
80 flatMap(provision -> Optional.ofNullable(provision.getMapping())).
81 filter(mapping -> !mapping.getItems().isEmpty()).isPresent()).
82 map(ResourceTO::getKey).
83 collect(Collectors.toList()));
84 add(dropdownResourceField.setNullValid(false).addRequiredLabel().setOutputMarkupId(true));
85
86 String connObjectKeyFieldId = "connObjectKeyValue";
87 AjaxTextFieldPanel connObjectKeyField = new AjaxTextFieldPanel(
88 "connObjectKeyValue",
89 "connObjectKeyValue",
90 new PropertyModel<>(linkedAccountTO, "connObjectKeyValue"),
91 false);
92 connObjectKeyField.setOutputMarkupId(true);
93 connObjectKeyField.addRequiredLabel();
94 connObjectKeyField.setChoices(List.of());
95 connObjectKeyField.setEnabled(false);
96 add(connObjectKeyField);
97
98 dropdownResourceField.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
99
100 private static final long serialVersionUID = -1107858522700306810L;
101
102 @Override
103 protected void onUpdate(final AjaxRequestTarget target) {
104 boolean enabled = dropdownResourceField.getModelObject() != null
105 && !dropdownResourceField.getModelObject().isEmpty();
106 connObjectKeyField.setEnabled(enabled);
107 if (enabled) {
108 setConnObjectFieldChoices(connObjectKeyField, dropdownResourceField.getModelObject(), null);
109 }
110 target.add(connObjectKeyField);
111 }
112 });
113
114 connObjectKeyField.getField().setMarkupId(connObjectKeyFieldId);
115 connObjectKeyField.getField().add(new AjaxEventBehavior(Constants.ON_KEYDOWN) {
116
117 private static final long serialVersionUID = 3533589614190959822L;
118
119 @Override
120 protected void onEvent(final AjaxRequestTarget target) {
121 String searchTerm = connObjectKeyField.getField().getInput();
122 if (StringUtils.isNotBlank(searchTerm) && searchTerm.length() > 1) {
123 setConnObjectFieldChoices(connObjectKeyField, dropdownResourceField.getModelObject(), searchTerm);
124
125
126 target.appendJavaScript(connObjectKeyFieldValues.isEmpty()
127 ? "$('#" + connObjectKeyFieldId + "-autocomplete-container').hide();"
128 : "var simulatedEvent = new KeyboardEvent('keydown', {keyCode: 40, which: 40}); "
129 + "document.getElementById('" + connObjectKeyFieldId + "').dispatchEvent(simulatedEvent);");
130 }
131 }
132
133 @Override
134 protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
135 super.updateAjaxAttributes(attributes);
136
137 AjaxCallListener listener = new AjaxCallListener() {
138
139 private static final long serialVersionUID = 2208168001920794667L;
140
141 @Override
142 public CharSequence getPrecondition(final Component component) {
143
144
145 return "var keycode = Wicket.Event.keyCode(attrs.event); "
146 + "if ((keycode == 40) || (keycode == 38)) {return false;} return true;";
147 }
148 };
149 attributes.getAjaxCallListeners().add(listener);
150 attributes.setThrottlingSettings(
151 new ThrottlingSettings("id", Duration.of(1, ChronoUnit.SECONDS), true));
152 }
153 });
154 }
155
156 private void setConnObjectFieldChoices(
157 final AjaxTextFieldPanel ajaxTextFieldPanel,
158 final String resource,
159 final String searchTerm) {
160
161 AtomicReference<String> resourceRemoteKey = new AtomicReference<>(ConnIdSpecialName.NAME);
162 try {
163 resourceRestClient.read(resource).getProvision(AnyTypeKind.USER.name()).
164 flatMap(provision -> Optional.ofNullable(provision.getMapping())).
165 flatMap(mapping -> mapping.getConnObjectKeyItem()).
166 ifPresent(connObjectKeyItem -> resourceRemoteKey.set(connObjectKeyItem.getExtAttrName()));
167 } catch (Exception ex) {
168 LOG.error("While reading mapping for resource {}", resource, ex);
169 }
170
171 ConnObjectTOQuery.Builder builder = new ConnObjectTOQuery.Builder().size(SEARCH_SIZE);
172 if (StringUtils.isNotBlank(searchTerm)) {
173 builder.fiql(SyncopeClient.getConnObjectTOFiqlSearchConditionBuilder().
174 is(resourceRemoteKey.get()).equalTo(searchTerm + "*").query()).build();
175 }
176 Pair<String, List<ConnObject>> items = resourceRestClient.searchConnObjects(
177 resource,
178 AnyTypeKind.USER.name(),
179 builder,
180 new SortParam<>(resourceRemoteKey.get(), true));
181
182 connObjectKeyFieldValues = items.getRight().stream().
183 map(item -> item.getAttr(resourceRemoteKey.get()).map(attr -> attr.getValues().get(0)).orElse(null)).
184 filter(Objects::nonNull).
185 collect(Collectors.toList());
186 ajaxTextFieldPanel.setChoices(connObjectKeyFieldValues);
187 }
188 }