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.io.Serializable;
22 import java.util.ArrayList;
23 import java.util.Comparator;
24 import java.util.LinkedHashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Optional;
28 import java.util.stream.Collectors;
29 import org.apache.commons.collections4.CollectionUtils;
30 import org.apache.commons.collections4.ListUtils;
31 import org.apache.commons.lang3.StringUtils;
32 import org.apache.commons.lang3.time.DateFormatUtils;
33 import org.apache.commons.lang3.time.FastDateFormat;
34 import org.apache.syncope.client.console.SyncopeConsoleSession;
35 import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
36 import org.apache.syncope.client.console.rest.SchemaRestClient;
37 import org.apache.syncope.client.console.wicket.markup.html.form.BinaryFieldPanel;
38 import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel;
39 import org.apache.syncope.client.ui.commons.SchemaUtils;
40 import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
41 import org.apache.syncope.client.ui.commons.markup.html.form.AbstractFieldPanel;
42 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
43 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDateFieldPanel;
44 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDateTimeFieldPanel;
45 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
46 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxSpinnerFieldPanel;
47 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
48 import org.apache.syncope.client.ui.commons.markup.html.form.EncryptedFieldPanel;
49 import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
50 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
51 import org.apache.syncope.common.lib.Attr;
52 import org.apache.syncope.common.lib.SyncopeConstants;
53 import org.apache.syncope.common.lib.to.AnyTO;
54 import org.apache.syncope.common.lib.to.AnyTypeClassTO;
55 import org.apache.syncope.common.lib.to.PlainSchemaTO;
56 import org.apache.syncope.common.lib.to.SchemaTO;
57 import org.apache.syncope.common.lib.types.AttrSchemaType;
58 import org.apache.syncope.common.lib.types.SchemaType;
59 import org.apache.wicket.PageReference;
60 import org.apache.wicket.extensions.wizard.WizardModel.ICondition;
61 import org.apache.wicket.extensions.wizard.WizardStep;
62 import org.apache.wicket.markup.head.IHeaderResponse;
63 import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
64 import org.apache.wicket.markup.html.form.FormComponent;
65 import org.apache.wicket.markup.html.form.IChoiceRenderer;
66 import org.apache.wicket.markup.html.list.ListItem;
67 import org.apache.wicket.markup.html.panel.Panel;
68 import org.apache.wicket.model.IModel;
69 import org.apache.wicket.model.Model;
70 import org.apache.wicket.model.PropertyModel;
71 import org.apache.wicket.model.util.ListModel;
72 import org.apache.wicket.spring.injection.annot.SpringBean;
73
74 public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends WizardStep implements ICondition {
75
76 private static final long serialVersionUID = 8931397230194043674L;
77
78 @SpringBean
79 protected AnyTypeClassRestClient anyTypeClassRestClient;
80
81 @SpringBean
82 protected SchemaRestClient schemaRestClient;
83
84 protected final Comparator<Attr> attrComparator = new AttrComparator();
85
86 protected final AnyTO anyTO;
87
88 protected AnyTO previousObject;
89
90 protected final List<String> whichAttrs;
91
92 protected final Map<String, S> schemas = new LinkedHashMap<>();
93
94 protected final IModel<List<Attr>> attrs;
95
96 protected final List<String> anyTypeClasses;
97
98 protected String fileKey = "";
99
100 protected final AjaxWizard.Mode mode;
101
102 public AbstractAttrsWizardStep(
103 final AnyTO anyTO,
104 final AjaxWizard.Mode mode,
105 final List<String> anyTypeClasses,
106 final List<String> whichAttrs) {
107
108 super();
109 this.anyTypeClasses = anyTypeClasses;
110 this.attrs = new ListModel<>(List.of());
111
112 this.setOutputMarkupId(true);
113
114 this.mode = mode;
115 this.anyTO = anyTO;
116 this.whichAttrs = whichAttrs;
117 }
118
119 protected List<Attr> loadAttrs() {
120 List<String> classes = new ArrayList<>(anyTypeClasses);
121 classes.addAll(anyTypeClassRestClient.list(anyTO.getAuxClasses()).stream().
122 map(AnyTypeClassTO::getKey).collect(Collectors.toList()));
123 setSchemas(classes);
124 setAttrs();
125 return getAttrsFromTO();
126 }
127
128 protected boolean reoderSchemas() {
129 return !whichAttrs.isEmpty();
130 }
131
132 protected abstract SchemaType getSchemaType();
133
134 protected void setSchemas(final List<String> anyTypeClasses) {
135 setSchemas(anyTypeClasses, schemas);
136 }
137
138 protected void setSchemas(final List<String> anyTypeClasses, final Map<String, S> scs) {
139 List<S> allSchemas = anyTypeClasses.isEmpty()
140 ? List.of()
141 : schemaRestClient.getSchemas(getSchemaType(), null, anyTypeClasses.toArray(String[]::new));
142
143 scs.clear();
144
145 if (!allSchemas.isEmpty() && reoderSchemas()) {
146
147 allSchemas.removeAll(allSchemas.stream().
148 filter(schemaTO -> !whichAttrs.contains(schemaTO.getKey())).collect(Collectors.toSet()));
149 }
150
151 allSchemas.forEach(schemaTO -> scs.put(schemaTO.getKey(), schemaTO));
152 }
153
154 @Override
155 public void renderHead(final IHeaderResponse response) {
156 super.renderHead(response);
157 if (CollectionUtils.isEmpty(attrs.getObject())) {
158 response.render(OnDomReadyHeaderItem.forScript(
159 String.format("$('#emptyPlaceholder').append(\"%s\"); $('#attributes').hide();",
160 getString("attribute.empty.list"))));
161 }
162 }
163
164 protected abstract void setAttrs();
165
166 protected abstract List<Attr> getAttrsFromTO();
167
168 @Override
169 public boolean evaluate() {
170 this.attrs.setObject(loadAttrs());
171 return !attrs.getObject().isEmpty();
172 }
173
174 public PageReference getPageReference() {
175
176
177 return null;
178 }
179
180 @SuppressWarnings({ "rawtypes", "unchecked" })
181 protected FieldPanel getFieldPanel(final PlainSchemaTO plainSchema) {
182 final boolean required;
183 final boolean readOnly;
184 final AttrSchemaType type;
185 final boolean jexlHelp;
186
187 if (mode == AjaxWizard.Mode.TEMPLATE) {
188 required = false;
189 readOnly = false;
190 type = AttrSchemaType.String;
191 jexlHelp = true;
192 } else {
193 required = plainSchema.getMandatoryCondition().equalsIgnoreCase("true");
194 readOnly = plainSchema.isReadonly();
195 type = plainSchema.getType();
196 jexlHelp = false;
197 }
198
199 FieldPanel panel;
200 switch (type) {
201 case Boolean:
202 panel = new AjaxCheckBoxPanel(
203 "panel",
204 plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
205 new Model<>(),
206 true);
207 panel.setRequired(required);
208 break;
209
210 case Date:
211 String datePattern = plainSchema.getConversionPattern() == null
212 ? DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.getPattern()
213 : plainSchema.getConversionPattern();
214
215 if (StringUtils.containsIgnoreCase(datePattern, "H")) {
216 panel = new AjaxDateTimeFieldPanel(
217 "panel",
218 plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
219 new Model<>(),
220 FastDateFormat.getInstance(datePattern));
221 } else {
222 panel = new AjaxDateFieldPanel(
223 "panel",
224 plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
225 new Model<>(),
226 FastDateFormat.getInstance(datePattern));
227 }
228
229 if (required) {
230 panel.addRequiredLabel();
231 }
232
233 break;
234
235 case Enum:
236 panel = new AjaxDropDownChoicePanel<>("panel",
237 plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true);
238 ((AjaxDropDownChoicePanel<String>) panel).setChoices(SchemaUtils.getEnumeratedValues(plainSchema));
239
240 if (StringUtils.isNotBlank(plainSchema.getEnumerationKeys())) {
241 ((AjaxDropDownChoicePanel) panel).setChoiceRenderer(new IChoiceRenderer<String>() {
242
243 private static final long serialVersionUID = -3724971416312135885L;
244
245 private final Map<String, String> valueMap = SchemaUtils.getEnumeratedKeyValues(plainSchema);
246
247 @Override
248 public String getDisplayValue(final String value) {
249 return valueMap.get(value) == null ? value : valueMap.get(value);
250 }
251
252 @Override
253 public String getIdValue(final String value, final int i) {
254 return value;
255 }
256
257 @Override
258 public String getObject(
259 final String id, final IModel<? extends List<? extends String>> choices) {
260 return id;
261 }
262 });
263 }
264
265 if (required) {
266 panel.addRequiredLabel();
267 }
268 break;
269
270 case Long:
271 panel = new AjaxSpinnerFieldPanel.Builder<Long>().enableOnChange().build(
272 "panel",
273 plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
274 Long.class,
275 new Model<>());
276
277 if (required) {
278 panel.addRequiredLabel();
279 }
280 break;
281
282 case Double:
283 panel = new AjaxSpinnerFieldPanel.Builder<Double>().enableOnChange().step(0.1).build(
284 "panel",
285 plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
286 Double.class,
287 new Model<>());
288
289 if (required) {
290 panel.addRequiredLabel();
291 }
292 break;
293
294 case Binary:
295 PageReference pageRef = getPageReference();
296 panel = new BinaryFieldPanel(
297 "panel",
298 plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
299 new Model<>(),
300 plainSchema.getMimeType(),
301 fileKey) {
302
303 private static final long serialVersionUID = -3268213909514986831L;
304
305 @Override
306 protected PageReference getPageReference() {
307 return pageRef;
308 }
309 };
310 if (required) {
311 panel.addRequiredLabel();
312 }
313 break;
314
315 case Encrypted:
316 panel = SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN.equals(plainSchema.getConversionPattern())
317 ? new AjaxTextFieldPanel("panel",
318 plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true)
319 : new EncryptedFieldPanel("panel",
320 plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true);
321
322 if (required) {
323 panel.addRequiredLabel();
324 }
325 break;
326
327 default:
328 panel = new AjaxTextFieldPanel("panel",
329 plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true);
330
331 if (jexlHelp) {
332 AjaxTextFieldPanel.class.cast(panel).enableJexlHelp();
333 }
334
335 if (required) {
336 panel.addRequiredLabel();
337 }
338 }
339
340 panel.setReadOnly(readOnly);
341
342 return panel;
343 }
344
345 protected FormComponent<?> checkboxToggle(
346 final Attr attr,
347 final AbstractFieldPanel<?> panel,
348 final boolean isMultivalue) {
349
350
351 return null;
352 }
353
354 private class AttrComparator implements Comparator<Attr>, Serializable {
355
356 private static final long serialVersionUID = -5105030477767941060L;
357
358 @Override
359 public int compare(final Attr left, final Attr right) {
360 if (left == null || StringUtils.isEmpty(left.getSchema())) {
361 return -1;
362 }
363 if (right == null || StringUtils.isEmpty(right.getSchema())) {
364 return 1;
365 } else if (AbstractAttrsWizardStep.this.reoderSchemas()) {
366 int leftIndex = AbstractAttrsWizardStep.this.whichAttrs.indexOf(left.getSchema());
367 int rightIndex = AbstractAttrsWizardStep.this.whichAttrs.indexOf(right.getSchema());
368
369 if (leftIndex > rightIndex) {
370 return 1;
371 } else if (leftIndex < rightIndex) {
372 return -1;
373 } else {
374 return 0;
375 }
376 } else {
377 return left.getSchema().compareTo(right.getSchema());
378 }
379 }
380 }
381
382 public static class Schemas extends Panel {
383
384 private static final long serialVersionUID = -2447602429647965090L;
385
386 public Schemas(final String id) {
387 super(id);
388 }
389 }
390
391 protected abstract class PlainSchemas<T> extends Schemas {
392
393 private static final long serialVersionUID = 8315035592714180404L;
394
395 public PlainSchemas(final String id) {
396 super(id);
397 }
398
399 @SuppressWarnings({ "unchecked", "rawtypes" })
400 protected AbstractFieldPanel<?> setPanel(
401 final Map<String, PlainSchemaTO> schemas,
402 final ListItem<Attr> item,
403 final boolean setReadOnly) {
404
405 Attr attr = item.getModelObject();
406 final boolean isMultivalue = mode != AjaxWizard.Mode.TEMPLATE
407 && schemas.get(attr.getSchema()).isMultivalue();
408
409 AbstractFieldPanel<?> panel = getFieldPanel(schemas.get(attr.getSchema()));
410 if (isMultivalue) {
411
412 panel = new MultiFieldPanel.Builder<>(
413 new PropertyModel<>(attr, "values")).build(
414 "panel",
415 attr.getSchema(),
416 FieldPanel.class.cast(panel)).setFormAsMultipart(true);
417
418 MultiFieldPanel.class.cast(panel).setReadOnly(schemas.get(attr.getSchema()).isReadonly());
419 MultiFieldPanel.class.cast(panel).setFormReadOnly(setReadOnly);
420 } else {
421 FieldPanel.class.cast(panel).setNewModel(attr.getValues()).setReadOnly(setReadOnly);
422 }
423 item.add(panel);
424
425 setExternalAction(attr, panel);
426
427 return panel;
428 }
429
430 protected void setExternalAction(final Attr attr, final AbstractFieldPanel<?> panel) {
431 if (previousObject == null) {
432 return;
433 }
434
435 Optional<Attr> prevAttr = previousObject.getPlainAttr(attr.getSchema());
436 if (prevAttr.map(a -> !ListUtils.isEqualList(
437 a.getValues().stream().filter(StringUtils::isNotBlank).collect(Collectors.toList()),
438 attr.getValues().stream().filter(StringUtils::isNotBlank).collect(Collectors.toList()))).
439 orElseGet(() -> attr.getValues().stream().anyMatch(StringUtils::isNotBlank))) {
440
441 List<String> oldValues = prevAttr.map(Attr::getValues).orElse(List.of());
442 panel.showExternAction(new LabelInfo("externalAction", oldValues));
443 }
444 }
445 }
446 }