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;
20
21 import java.io.Serializable;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.InvocationTargetException;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.List;
27 import java.util.Optional;
28 import org.apache.commons.lang3.StringUtils;
29 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
30 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksTogglePanel;
31 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
32 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
33 import org.apache.syncope.client.ui.commons.Constants;
34 import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormChoiceComponentUpdatingBehavior;
35 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
36 import org.apache.wicket.AttributeModifier;
37 import org.apache.wicket.Component;
38 import org.apache.wicket.PageReference;
39 import org.apache.wicket.ajax.AjaxEventBehavior;
40 import org.apache.wicket.ajax.AjaxRequestTarget;
41 import org.apache.wicket.core.util.lang.PropertyResolver;
42 import org.apache.wicket.event.IEvent;
43 import org.apache.wicket.markup.html.WebMarkupContainer;
44 import org.apache.wicket.markup.html.basic.Label;
45 import org.apache.wicket.markup.html.form.Check;
46 import org.apache.wicket.markup.html.form.CheckGroup;
47 import org.apache.wicket.markup.html.form.CheckGroupSelector;
48 import org.apache.wicket.markup.html.list.ListItem;
49 import org.apache.wicket.markup.html.list.ListView;
50 import org.apache.wicket.model.IModel;
51 import org.apache.wicket.model.Model;
52 import org.apache.wicket.model.ResourceModel;
53 import org.apache.wicket.request.cycle.RequestCycle;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 public abstract class ListViewPanel<T extends Serializable> extends WizardMgtPanel<T> {
58
59 private static final long serialVersionUID = -7982691107029848579L;
60
61 private static final Logger LOG = LoggerFactory.getLogger(ListViewPanel.class);
62
63 private ActionLinksTogglePanel<T> togglePanel;
64
65 public enum CheckAvailability {
66
67
68
69
70 NONE,
71
72
73
74 AVAILABLE,
75
76
77
78 DISABLED
79
80 }
81
82 private final CheckGroupSelector groupSelector;
83
84 private final Model<CheckAvailability> check;
85
86 private final ListView<T> beans;
87
88 private final List<T> listOfItems;
89
90
91
92
93
94
95
96
97
98
99 private ListViewPanel(
100 final String id,
101 final List<T> list,
102 final Class<T> reference,
103 final List<String> includes,
104 final ActionsPanel<T> actions,
105 final CheckAvailability check,
106 final boolean reuseItem,
107 final boolean wizardInModal,
108 final boolean captionVisible,
109 final IModel<? extends Collection<T>> model) {
110
111 super(id, wizardInModal);
112 setOutputMarkupId(true);
113
114 togglePanel = getTogglePanel();
115
116 this.check = Model.of(check);
117
118 WebMarkupContainer captionContainer = new WebMarkupContainer("captionContainer");
119 captionContainer.setOutputMarkupPlaceholderTag(true);
120 captionContainer.setVisible(captionVisible);
121 addInnerObject(captionContainer);
122
123 Label caption = new Label("caption", new ResourceModel("listview.caption", StringUtils.EMPTY));
124 captionContainer.add(caption);
125
126 CheckGroup<T> checkGroup = new CheckGroup<>("group", model);
127 checkGroup.setOutputMarkupId(true);
128 checkGroup.add(new IndicatorAjaxFormChoiceComponentUpdatingBehavior() {
129
130 private static final long serialVersionUID = -151291731388673682L;
131
132 @Override
133 protected void onUpdate(final AjaxRequestTarget target) {
134
135 }
136 });
137 addInnerObject(checkGroup);
138
139 groupSelector = new CheckGroupSelector("groupselector", checkGroup);
140 addInnerObject(groupSelector.setOutputMarkupId(true).
141 setOutputMarkupPlaceholderTag(true).
142 setVisible(this.check.getObject() == CheckAvailability.AVAILABLE).
143 setEnabled(this.check.getObject() == CheckAvailability.AVAILABLE));
144
145 final List<String> toBeIncluded;
146 if (includes == null || includes.isEmpty()) {
147 toBeIncluded = new ArrayList<>();
148 for (Field field : reference.getDeclaredFields()) {
149 toBeIncluded.add(field.getName());
150 }
151 } else {
152 toBeIncluded = includes;
153 }
154
155 if (toBeIncluded.isEmpty()) {
156 LOG.warn("No field has been retrieved from {}", reference.getName());
157 listOfItems = new ArrayList<>();
158 } else if (list == null || list.isEmpty()) {
159 LOG.info("No item to be shown");
160 listOfItems = new ArrayList<>();
161 } else {
162 listOfItems = list;
163 LOG.debug("Show fields {}", toBeIncluded);
164 }
165
166 addInnerObject(header(toBeIncluded));
167
168 beans = new ListView<>("beans", listOfItems) {
169
170 private static final long serialVersionUID = -9112553137618363167L;
171
172 @Override
173 protected void populateItem(final ListItem<T> beanItem) {
174 beanItem.add(new Check<>("check", beanItem.getModel(), checkGroup).setOutputMarkupId(true).
175 setOutputMarkupPlaceholderTag(true).
176 setVisible(ListViewPanel.this.check.getObject() == CheckAvailability.AVAILABLE
177 || ListViewPanel.this.check.getObject() == CheckAvailability.DISABLED).
178 setEnabled(ListViewPanel.this.check.getObject() == CheckAvailability.AVAILABLE));
179
180 final T bean = beanItem.getModelObject();
181
182 final ListView<String> fields = new ListView<>("fields", toBeIncluded) {
183
184 private static final long serialVersionUID = -9112553137618363167L;
185
186 @Override
187 protected void populateItem(final ListItem<String> fieldItem) {
188 fieldItem.add(getValueComponent(fieldItem.getModelObject(), bean));
189 if (togglePanel != null) {
190 fieldItem.add(new AttributeModifier("style", "cursor: pointer;"));
191 fieldItem.add(new AjaxEventBehavior(Constants.ON_CLICK) {
192
193 private static final long serialVersionUID = -9027652037484739586L;
194
195 @Override
196 protected String findIndicatorId() {
197 return StringUtils.EMPTY;
198 }
199
200 @Override
201 protected void onEvent(final AjaxRequestTarget target) {
202 togglePanel.toggleWithContent(
203 target,
204 actions.cloneWithLabels("actions", new Model<>(bean)),
205 bean);
206 }
207 });
208 }
209 }
210 };
211
212 beanItem.add(fields);
213
214 if (togglePanel == null) {
215 beanItem.add(actions.clone("actions", new Model<>(bean)));
216 } else {
217 beanItem.add(new ActionsPanel<>("actions", new Model<>(bean)).setVisible(false).setEnabled(false));
218 }
219 }
220 };
221 beans.setOutputMarkupId(true);
222 beans.setReuseItems(reuseItem);
223 beans.setRenderBodyOnly(true);
224 checkGroup.add(beans);
225 }
226
227 protected ListView<String> header(final List<String> labels) {
228 return new ListView<>("names", labels) {
229
230 private static final long serialVersionUID = -9112553137618363167L;
231
232 @Override
233 protected void populateItem(final ListItem<String> item) {
234 item.add(new Label(Constants.NAME_FIELD_NAME,
235 new ResourceModel(item.getModelObject(), item.getModelObject())));
236 }
237 };
238 }
239
240
241
242
243
244
245 public void refreshList(final List<T> elements) {
246 beans.setList(elements);
247 }
248
249 public void setCheckAvailability(final CheckAvailability check) {
250
251 this.check.setObject(check);
252
253 RequestCycle.get().find(AjaxRequestTarget.class).ifPresent(t -> {
254
255 t.add(groupSelector.setVisible(check == CheckAvailability.AVAILABLE), groupSelector.getMarkupId());
256
257 t.add(ListViewPanel.this, getMarkupId());
258 });
259 }
260
261 protected abstract Component getValueComponent(String key, T bean);
262
263
264
265
266
267
268 public static class Builder<T extends Serializable> extends WizardMgtPanel.Builder<T> {
269
270 private static final long serialVersionUID = -3643771352897992172L;
271
272 private IModel<? extends Collection<T>> model = Model.of(List.of());
273
274 private final List<String> includes = new ArrayList<>();
275
276 private final ActionsPanel<T> actions;
277
278 private List<T> items;
279
280 private CheckAvailability check = CheckAvailability.NONE;
281
282 private boolean reuseItem = true;
283
284 private boolean captionVisible = true;
285
286 private final Class<T> reference;
287
288 public Builder(final Class<T> reference, final PageReference pageRef) {
289 super(pageRef);
290 this.reference = reference;
291 this.items = null;
292 this.actions = new ActionsPanel<>("actions", null);
293 }
294
295 public Builder<T> setModel(final IModel<? extends Collection<T>> model) {
296 this.model = model;
297 return this;
298 }
299
300
301
302
303
304
305
306 public Builder<T> setItems(final List<T> items) {
307 this.items = items;
308 return this;
309 }
310
311
312
313
314
315
316
317 public Builder<T> addItem(final T item) {
318 if (item == null) {
319 return this;
320 }
321
322 if (this.items == null) {
323 this.items = new ArrayList<>();
324 }
325
326 this.items.add(item);
327 return this;
328 }
329
330 public Builder<T> withChecks(final CheckAvailability check) {
331 this.check = check;
332 return this;
333 }
334
335 public Builder<T> setReuseItem(final boolean reuseItem) {
336 this.reuseItem = reuseItem;
337 return this;
338 }
339
340 public Builder<T> setCaptionVisible(final boolean captionVisible) {
341 this.captionVisible = captionVisible;
342 return this;
343 }
344
345
346
347
348
349
350
351 public Builder<T> includes(final String... includes) {
352 for (String include : includes) {
353 if (include != null && !this.includes.contains(include)) {
354 this.includes.add(include);
355 }
356 }
357 return this;
358 }
359
360
361
362
363
364
365
366
367
368 public Builder<T> addAction(
369 final ActionLink<T> link, final ActionLink.ActionType type, final String entitlements) {
370 return addAction(link, type, entitlements, false);
371 }
372
373
374
375
376
377
378
379
380
381
382 public Builder<T> addAction(
383 final ActionLink<T> link,
384 final ActionLink.ActionType type,
385 final String entitlements,
386 final boolean onConfirm) {
387 actions.add(link, type, entitlements, onConfirm).hideLabel();
388 return this;
389 }
390
391
392
393
394
395
396
397
398 protected Component getValueComponent(final String key, final T bean) {
399 LOG.debug("Processing field {}", key);
400
401 Object value;
402 try {
403 value = PropertyResolver.getPropertyGetter(key, bean).invoke(bean);
404 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
405 LOG.error("Error retrieving value for field {}", key, e);
406 value = StringUtils.EMPTY;
407 }
408
409 LOG.debug("Field value {}", value);
410
411 return Optional.ofNullable(value)
412 .map(o -> new Label("field", new ResourceModel(o.toString(), o.toString())))
413 .orElseGet(() -> new Label("field", StringUtils.EMPTY));
414 }
415
416 protected T getActualItem(final T item, final List<T> list) {
417 return item == null
418 ? null
419 : list.stream().filter(item::equals).findAny().orElse(null);
420 }
421
422 @Override
423 protected WizardMgtPanel<T> newInstance(final String id, final boolean wizardInModal) {
424 return new ListViewPanel<>(
425 id, items, reference, includes, actions, check, reuseItem, wizardInModal, captionVisible, model) {
426
427 private static final long serialVersionUID = -1715389337530657988L;
428
429 @Override
430 protected Component getValueComponent(final String key, final T bean) {
431 return Builder.this.getValueComponent(key, bean);
432 }
433
434 @Override
435 protected T getActualItem(final T item, final List<T> list) {
436 return Builder.this.getActualItem(item, list);
437 }
438
439 @Override
440 protected void customActionCallback(final AjaxRequestTarget target) {
441 Builder.this.customActionCallback(target);
442 }
443
444 @Override
445 protected void customActionOnFinishCallback(final AjaxRequestTarget target) {
446 Builder.this.customActionOnFinishCallback(target);
447 }
448
449 @Override
450 protected void customActionOnCancelCallback(final AjaxRequestTarget target) {
451 Builder.this.customActionOnCancelCallback(target);
452 }
453
454 @Override
455 protected ActionLinksTogglePanel<T> getTogglePanel() {
456 return Builder.this.getTogglePanel();
457 }
458 };
459 }
460
461 protected ActionLinksTogglePanel<T> getTogglePanel() {
462 return null;
463 }
464
465 protected void customActionCallback(final AjaxRequestTarget target) {
466 }
467
468 protected void customActionOnCancelCallback(final AjaxRequestTarget target) {
469 }
470
471 protected void customActionOnFinishCallback(final AjaxRequestTarget target) {
472 }
473 }
474
475 @Override
476 @SuppressWarnings("unchecked")
477 public void onEvent(final IEvent<?> event) {
478 if (event.getPayload() instanceof AjaxWizard.NewItemEvent) {
479 final T item = ((AjaxWizard.NewItemEvent<T>) event.getPayload()).getItem();
480 final Optional<AjaxRequestTarget> target = ((AjaxWizard.NewItemEvent<T>) event.getPayload()).getTarget();
481
482 if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) {
483 final T old = getActualItem(item, ListViewPanel.this.listOfItems);
484 int indexOf = ListViewPanel.this.listOfItems.size();
485 if (old != null) {
486 indexOf = ListViewPanel.this.listOfItems.indexOf(old);
487 ListViewPanel.this.listOfItems.remove(old);
488 }
489 ListViewPanel.this.listOfItems.add(indexOf, item);
490 }
491
492 target.ifPresent(t -> t.add(ListViewPanel.this));
493 super.onEvent(event);
494 } else if (event.getPayload() instanceof ListViewPanel.ListViewReload) {
495 final ListViewPanel.ListViewReload<?> payload = (ListViewPanel.ListViewReload<?>) event.getPayload();
496 if (payload.getItems() != null) {
497 ListViewPanel.this.listOfItems.clear();
498 try {
499 ListViewPanel.this.listOfItems.addAll((List<T>) payload.getItems());
500 } catch (RuntimeException re) {
501 LOG.warn("Error reloading items", re);
502 }
503 }
504 payload.getTarget().add(ListViewPanel.this);
505 } else {
506 super.onEvent(event);
507 }
508 }
509
510 protected abstract T getActualItem(T item, List<T> list);
511
512 public static class ListViewReload<T extends Serializable> implements Serializable {
513
514 private static final long serialVersionUID = 1509151005816590312L;
515
516 private final AjaxRequestTarget target;
517
518 private final List<T> items;
519
520 public ListViewReload(final AjaxRequestTarget target) {
521 this.target = target;
522 this.items = null;
523 }
524
525 public ListViewReload(final List<T> items, final AjaxRequestTarget target) {
526 this.target = target;
527 this.items = items;
528 }
529
530 public AjaxRequestTarget getTarget() {
531 return target;
532 }
533
534 public List<T> getItems() {
535 return items;
536 }
537 }
538
539 protected ActionLinksTogglePanel<T> getTogglePanel() {
540 return null;
541 }
542 }