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.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Optional;
26 import java.util.stream.Collectors;
27 import org.apache.commons.collections4.ListUtils;
28 import org.apache.commons.lang3.StringUtils;
29 import org.apache.syncope.client.console.panels.AnyDirectoryPanel;
30 import org.apache.syncope.client.console.panels.ListViewPanel;
31 import org.apache.syncope.client.console.panels.ListViewPanel.ListViewReload;
32 import org.apache.syncope.client.console.panels.search.AnyObjectSearchPanel;
33 import org.apache.syncope.client.console.panels.search.AnyObjectSelectionDirectoryPanel;
34 import org.apache.syncope.client.console.panels.search.AnySelectionDirectoryPanel;
35 import org.apache.syncope.client.console.panels.search.SearchClausePanel;
36 import org.apache.syncope.client.console.panels.search.SearchUtils;
37 import org.apache.syncope.client.console.rest.AnyObjectRestClient;
38 import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
39 import org.apache.syncope.client.console.rest.AnyTypeRestClient;
40 import org.apache.syncope.client.console.rest.RelationshipTypeRestClient;
41 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
42 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
43 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
44 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
45 import org.apache.syncope.client.lib.SyncopeClient;
46 import org.apache.syncope.client.ui.commons.Constants;
47 import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
48 import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
49 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
50 import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
51 import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
52 import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
53 import org.apache.syncope.common.lib.to.AnyObjectTO;
54 import org.apache.syncope.common.lib.to.AnyTO;
55 import org.apache.syncope.common.lib.to.AnyTypeTO;
56 import org.apache.syncope.common.lib.to.GroupableRelatableTO;
57 import org.apache.syncope.common.lib.to.RelationshipTO;
58 import org.apache.syncope.common.lib.to.RelationshipTypeTO;
59 import org.apache.syncope.common.lib.types.AnyEntitlement;
60 import org.apache.syncope.common.lib.types.AnyTypeKind;
61 import org.apache.wicket.Component;
62 import org.apache.wicket.PageReference;
63 import org.apache.wicket.ajax.AjaxRequestTarget;
64 import org.apache.wicket.event.Broadcast;
65 import org.apache.wicket.event.IEvent;
66 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
67 import org.apache.wicket.extensions.wizard.IWizard;
68 import org.apache.wicket.extensions.wizard.WizardModel.ICondition;
69 import org.apache.wicket.extensions.wizard.WizardStep;
70 import org.apache.wicket.markup.head.IHeaderResponse;
71 import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
72 import org.apache.wicket.markup.html.WebMarkupContainer;
73 import org.apache.wicket.markup.html.basic.Label;
74 import org.apache.wicket.markup.html.form.IChoiceRenderer;
75 import org.apache.wicket.markup.html.panel.Fragment;
76 import org.apache.wicket.markup.html.panel.Panel;
77 import org.apache.wicket.model.IModel;
78 import org.apache.wicket.model.PropertyModel;
79 import org.apache.wicket.model.ResourceModel;
80 import org.apache.wicket.model.util.ListModel;
81 import org.apache.wicket.spring.injection.annot.SpringBean;
82
83 public class Relationships extends WizardStep implements ICondition {
84
85 private static final long serialVersionUID = 855618618337931784L;
86
87 @SpringBean
88 protected RelationshipTypeRestClient relationshipTypeRestClient;
89
90 @SpringBean
91 protected AnyTypeRestClient anyTypeRestClient;
92
93 @SpringBean
94 protected AnyTypeClassRestClient anyTypeClassRestClient;
95
96 @SpringBean
97 protected AnyObjectRestClient anyObjectRestClient;
98
99 protected final AnyTO anyTO;
100
101 protected final Specification specification;
102
103 protected final PageReference pageRef;
104
105 public Relationships(final AnyWrapper<?> modelObject, final PageReference pageRef) {
106 super();
107 add(new Label("title", new ResourceModel("any.relationships")));
108
109 if (modelObject instanceof UserWrapper
110 && UserWrapper.class.cast(modelObject).getPreviousUserTO() != null
111 && !ListUtils.isEqualList(
112 UserWrapper.class.cast(modelObject).getInnerObject().getRelationships(),
113 UserWrapper.class.cast(modelObject).getPreviousUserTO().getRelationships())) {
114 add(new LabelInfo("changed", StringUtils.EMPTY));
115 } else {
116 add(new Label("changed", StringUtils.EMPTY));
117 }
118
119 this.anyTO = modelObject.getInnerObject();
120 this.specification = new Specification();
121 this.pageRef = pageRef;
122
123
124
125
126 add(getViewFragment().setRenderBodyOnly(true));
127
128 }
129
130 @Override
131 public Component getHeader(final String id, final Component parent, final IWizard wizard) {
132 return super.getHeader(id, parent, wizard).setVisible(false);
133 }
134
135 protected Fragment getViewFragment() {
136 Map<String, List<RelationshipTO>> relationships = new HashMap<>();
137 addRelationship(relationships, getCurrentRelationships().toArray(RelationshipTO[]::new));
138
139 Fragment viewFragment = new Fragment("relationships", "viewFragment", this);
140 viewFragment.setOutputMarkupId(true);
141
142 viewFragment.add(new Accordion("relationships", relationships.keySet().stream().
143 map(relationship -> new AbstractTab(new ResourceModel("relationship", relationship)) {
144
145 private static final long serialVersionUID = 1037272333056449378L;
146
147 @Override
148 public Panel getPanel(final String panelId) {
149 return new ListViewPanel.Builder<>(RelationshipTO.class, pageRef).
150 setItems(relationships.get(relationship)).
151 includes("otherEndType", "otherEndKey", "otherEndName").
152 addAction(new ActionLink<>() {
153
154 private static final long serialVersionUID = -6847033126124401556L;
155
156 @Override
157 public void onClick(final AjaxRequestTarget target, final RelationshipTO modelObject) {
158 removeRelationships(relationships, modelObject);
159 send(Relationships.this, Broadcast.DEPTH, new ListViewReload<>(target));
160 }
161 }, ActionType.DELETE, AnyEntitlement.UPDATE.getFor(anyTO.getType()), true).
162 build(panelId);
163 }
164 }).collect(Collectors.toList())) {
165
166 private static final long serialVersionUID = 1037272333056449379L;
167
168 @Override
169 public void renderHead(final IHeaderResponse response) {
170 super.renderHead(response);
171 if (relationships.isEmpty()) {
172 response.render(OnDomReadyHeaderItem.forScript(String.format(
173 "$('#emptyPlaceholder').append(\"%s\")", getString("relationships.empty.list"))));
174 }
175 }
176 });
177
178 ActionsPanel<RelationshipTO> panel = new ActionsPanel<>("actions", null);
179 viewFragment.add(panel);
180
181 panel.add(new ActionLink<>() {
182
183 private static final long serialVersionUID = 3257738274365467945L;
184
185 @Override
186 public void onClick(final AjaxRequestTarget target, final RelationshipTO ignore) {
187 Fragment addFragment = new Fragment("relationships", "addFragment", Relationships.this);
188 addOrReplace(addFragment);
189 addFragment.add(specification.setRenderBodyOnly(true));
190 target.add(Relationships.this);
191 }
192 }, ActionType.CREATE, AnyEntitlement.UPDATE.getFor(anyTO.getType())).hideLabel();
193
194 return viewFragment;
195 }
196
197 protected List<RelationshipTO> getCurrentRelationships() {
198 return anyTO instanceof GroupableRelatableTO
199 ? GroupableRelatableTO.class.cast(anyTO).getRelationships()
200 : List.of();
201 }
202
203 protected void addRelationship(
204 final Map<String, List<RelationshipTO>> relationships,
205 final RelationshipTO... rels) {
206
207 for (RelationshipTO relationship : rels) {
208 List<RelationshipTO> listrels;
209 if (relationships.containsKey(relationship.getType())) {
210 listrels = relationships.get(relationship.getType());
211 } else {
212 listrels = new ArrayList<>();
213 relationships.put(relationship.getType(), listrels);
214 }
215 listrels.add(relationship);
216 }
217 }
218
219 protected void addNewRelationships(final RelationshipTO... rels) {
220 getCurrentRelationships().addAll(List.of(rels));
221 }
222
223 protected void removeRelationships(
224 final Map<String, List<RelationshipTO>> relationships, final RelationshipTO... rels) {
225
226 List<RelationshipTO> currentRels = getCurrentRelationships();
227 for (RelationshipTO relationship : rels) {
228 currentRels.remove(relationship);
229 if (relationships.containsKey(relationship.getType())) {
230 List<RelationshipTO> rellist = relationships.get(relationship.getType());
231 rellist.remove(relationship);
232 if (rellist.isEmpty()) {
233 relationships.remove(relationship.getType());
234 }
235 }
236 }
237 }
238
239 @Override
240 public boolean evaluate() {
241
242 return !relationshipTypeRestClient.list().isEmpty();
243 }
244
245 public class Specification extends Panel {
246
247 private static final long serialVersionUID = 6199050589175839467L;
248
249 protected final RelationshipTO rel;
250
251 protected final AjaxDropDownChoicePanel<String> type;
252
253 protected final AjaxDropDownChoicePanel<AnyTypeTO> otherType;
254
255 protected final WebMarkupContainer container;
256
257 protected final Fragment emptyFragment;
258
259 protected final Fragment fragment;
260
261 protected AnyObjectSearchPanel anyObjectSearchPanel;
262
263 protected WizardMgtPanel<AnyWrapper<AnyObjectTO>> anyObjectDirectoryPanel;
264
265 public Specification() {
266 super("specification");
267 rel = new RelationshipTO();
268
269 List<String> availableRels = relationshipTypeRestClient.list().stream().
270 map(RelationshipTypeTO::getKey).collect(Collectors.toList());
271
272 type = new AjaxDropDownChoicePanel<>("type", "type", new PropertyModel<>(rel, "type"));
273 type.setChoices(availableRels);
274 add(type.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true).setRenderBodyOnly(true));
275
276 List<AnyTypeTO> availableTypes = anyTypeRestClient.listAnyTypes().stream().
277 filter(anyType -> anyType.getKind() != AnyTypeKind.GROUP
278 && anyType.getKind() != AnyTypeKind.USER).collect(Collectors.toList());
279
280 otherType = new AjaxDropDownChoicePanel<>("otherType", "otherType", new PropertyModel<>(rel, "otherType") {
281
282 private static final long serialVersionUID = -5861057041758169508L;
283
284 @Override
285 public AnyTypeTO getObject() {
286 for (AnyTypeTO obj : availableTypes) {
287 if (obj.getKey().equals(rel.getOtherEndType())) {
288 return obj;
289 }
290 }
291 return null;
292 }
293
294 @Override
295 public void setObject(final AnyTypeTO object) {
296 rel.setOtherEndType(Optional.ofNullable(object).map(AnyTypeTO::getKey).orElse(null));
297 }
298 }, false);
299 otherType.setChoices(availableTypes);
300 otherType.setChoiceRenderer(new IChoiceRenderer<>() {
301
302 private static final long serialVersionUID = -734743540442190178L;
303
304 @Override
305 public Object getDisplayValue(final AnyTypeTO object) {
306 return object.getKey();
307 }
308
309 @Override
310 public String getIdValue(final AnyTypeTO object, final int index) {
311 return object.getKey();
312 }
313
314 @Override
315 public AnyTypeTO getObject(final String id, final IModel<? extends List<? extends AnyTypeTO>> choices) {
316 return choices.getObject().stream().
317 filter(anyTypeTO -> id.equals(anyTypeTO.getKey())).findAny().orElse(null);
318 }
319 });
320
321 otherType.setEnabled(false);
322 add(otherType.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true));
323
324 container = new WebMarkupContainer("searchPanelContainer");
325 add(container.setOutputMarkupId(true));
326
327 emptyFragment = new Fragment("searchPanel", "emptyFragment", this);
328 container.add(emptyFragment.setRenderBodyOnly(true));
329
330 fragment = new Fragment("searchPanel", "searchFragment", Specification.this);
331
332 type.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
333
334 private static final long serialVersionUID = -1107858522700306810L;
335
336 @Override
337 protected void onUpdate(final AjaxRequestTarget target) {
338 container.addOrReplace(emptyFragment.setRenderBodyOnly(true));
339 otherType.setModelObject(null);
340
341 otherType.setEnabled(type.getModelObject() != null && !type.getModelObject().isEmpty());
342 target.add(otherType);
343 target.add(container);
344 }
345 });
346
347 otherType.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
348
349 private static final long serialVersionUID = -1107858522700306810L;
350
351 @Override
352 protected void onUpdate(final AjaxRequestTarget target) {
353 AnyTypeTO anyType = otherType.getModelObject();
354 if (anyType == null) {
355 container.addOrReplace(emptyFragment.setRenderBodyOnly(true));
356 } else {
357 setupFragment(anyType);
358 container.addOrReplace(fragment.setRenderBodyOnly(true));
359 }
360 target.add(container);
361 }
362 });
363 }
364
365 protected void setupFragment(final AnyTypeTO anyType) {
366 anyObjectSearchPanel = new AnyObjectSearchPanel.Builder(
367 anyType.getKey(),
368 new ListModel<>(new ArrayList<>()),
369 pageRef).
370 enableSearch(Specification.this).
371 build("searchPanel");
372 fragment.addOrReplace(anyObjectSearchPanel.setRenderBodyOnly(true));
373
374 anyObjectDirectoryPanel = new AnyObjectSelectionDirectoryPanel.Builder(
375 anyTypeClassRestClient.list(anyType.getClasses()),
376 anyObjectRestClient,
377 anyType.getKey(),
378 pageRef).
379 setFiql(SyncopeClient.getAnyObjectSearchConditionBuilder(anyType.getKey()).
380 is(Constants.KEY_FIELD_NAME).notNullValue().query()).
381 setWizardInModal(true).build("searchResultPanel");
382 fragment.addOrReplace(anyObjectDirectoryPanel.setRenderBodyOnly(true));
383 }
384
385 @Override
386 public void onEvent(final IEvent<?> event) {
387 if (event.getPayload() instanceof SearchClausePanel.SearchEvent) {
388 AjaxRequestTarget target =
389 SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget();
390 String fiql = SearchUtils.buildFIQL(anyObjectSearchPanel.getModel().getObject(),
391 SyncopeClient.getAnyObjectSearchConditionBuilder(anyObjectSearchPanel.getBackObjectType()));
392 AnyDirectoryPanel.class.cast(Specification.this.anyObjectDirectoryPanel).search(fiql, target);
393 } else if (event.getPayload() instanceof AnySelectionDirectoryPanel.ItemSelection) {
394 AjaxRequestTarget target =
395 AnySelectionDirectoryPanel.ItemSelection.class.cast(event.getPayload()).getTarget();
396
397 AnyTO right = AnySelectionDirectoryPanel.ItemSelection.class.cast(event.getPayload()).getSelection();
398 rel.setOtherEndKey(right.getKey());
399
400 Relationships.this.addNewRelationships(rel);
401
402 Relationships.this.addOrReplace(getViewFragment().setRenderBodyOnly(true));
403 target.add(Relationships.this);
404 } else {
405 super.onEvent(event);
406 }
407 }
408 }
409 }