1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.client.ui.commons.wizards;
20
21 import java.io.Serializable;
22 import java.util.ArrayList;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Optional;
26 import java.util.concurrent.Callable;
27 import java.util.concurrent.ExecutionException;
28 import java.util.concurrent.Future;
29 import java.util.concurrent.TimeUnit;
30 import java.util.concurrent.TimeoutException;
31 import org.apache.commons.lang3.tuple.Pair;
32 import org.apache.syncope.client.ui.commons.Constants;
33 import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
34 import org.apache.syncope.client.ui.commons.panels.SubmitableModalPanel;
35 import org.apache.syncope.client.ui.commons.panels.WizardModalPanel;
36 import org.apache.syncope.client.ui.commons.wizards.exception.CaptchaNotMatchingException;
37 import org.apache.wicket.Application;
38 import org.apache.wicket.Component;
39 import org.apache.wicket.PageReference;
40 import org.apache.wicket.Session;
41 import org.apache.wicket.ThreadContext;
42 import org.apache.wicket.WicketRuntimeException;
43 import org.apache.wicket.ajax.AjaxRequestTarget;
44 import org.apache.wicket.event.Broadcast;
45 import org.apache.wicket.event.IEventSink;
46 import org.apache.wicket.extensions.wizard.IWizardModel;
47 import org.apache.wicket.extensions.wizard.IWizardStep;
48 import org.apache.wicket.extensions.wizard.Wizard;
49 import org.apache.wicket.extensions.wizard.WizardModel;
50 import org.apache.wicket.extensions.wizard.WizardStep;
51 import org.apache.wicket.markup.html.list.ListItem;
52 import org.apache.wicket.markup.html.list.ListView;
53 import org.apache.wicket.model.CompoundPropertyModel;
54 import org.apache.wicket.model.IModel;
55 import org.apache.wicket.request.cycle.RequestCycle;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 public abstract class AjaxWizard<T extends Serializable> extends Wizard
60 implements SubmitableModalPanel, WizardModalPanel<T> {
61
62 private static final long serialVersionUID = -1272120742876833520L;
63
64 private final List<Component> outerObjects = new ArrayList<>();
65
66 public enum Mode {
67 CREATE,
68 EDIT,
69 TEMPLATE,
70 READONLY,
71 EDIT_APPROVAL;
72
73 }
74
75 protected static final Logger LOG = LoggerFactory.getLogger(AjaxWizard.class);
76
77 private T item;
78
79 private final Mode mode;
80
81 private IEventSink eventSink;
82
83 private final PageReference pageRef;
84
85
86
87
88
89
90
91
92
93
94 public AjaxWizard(
95 final String id,
96 final T item,
97 final WizardModel model,
98 final Mode mode,
99 final PageReference pageRef) {
100
101 super(id);
102 this.item = item;
103 this.mode = mode;
104 this.pageRef = pageRef;
105
106 if (mode == Mode.READONLY) {
107 model.setCancelVisible(false);
108 }
109
110 add(new ListView<>("outerObjectsRepeater", outerObjects) {
111
112 private static final long serialVersionUID = -9180479401817023838L;
113
114 @Override
115 protected void populateItem(final ListItem<Component> item) {
116 item.add(item.getModelObject());
117 }
118
119 });
120
121 setOutputMarkupId(true);
122 setDefaultModel(new CompoundPropertyModel<>(this));
123 init(model);
124 }
125
126
127
128
129
130
131
132
133
134 public final AjaxWizard<T> addOuterObject(final List<Component> childs) {
135 outerObjects.addAll(childs);
136 return this;
137 }
138
139 public AjaxWizard<T> setEventSink(final IEventSink eventSink) {
140 this.eventSink = eventSink;
141 return this;
142 }
143
144 @Override
145 protected void init(final IWizardModel wizardModel) {
146 super.init(wizardModel);
147 getForm().remove(FEEDBACK_ID);
148
149 if (mode == Mode.READONLY) {
150 Iterator<IWizardStep> iter = wizardModel.stepIterator();
151 while (iter.hasNext()) {
152 WizardStep.class.cast(iter.next()).setEnabled(false);
153 }
154 }
155 }
156
157 @Override
158 protected Component newButtonBar(final String id) {
159 return new AjaxWizardMgtButtonBar<>(id, this, mode);
160 }
161
162 protected abstract void onCancelInternal();
163
164 protected abstract void sendError(Exception exception);
165
166 protected abstract void sendWarning(String message);
167
168 protected abstract Future<Pair<Serializable, Serializable>> execute(
169 Callable<Pair<Serializable, Serializable>> future);
170
171
172
173
174
175
176
177 protected abstract Pair<Serializable, Serializable> onApplyInternal(AjaxRequestTarget target);
178
179 protected abstract long getMaxWaitTimeInSeconds();
180
181 @Override
182 public final void onCancel() {
183 AjaxRequestTarget target = RequestCycle.get().find(AjaxRequestTarget.class).orElse(null);
184 try {
185 onCancelInternal();
186 if (eventSink == null) {
187 send(AjaxWizard.this, Broadcast.BUBBLE, new NewItemCancelEvent<>(item, target));
188 } else {
189 send(eventSink, Broadcast.EXACT, new NewItemCancelEvent<>(item, target));
190 }
191 } catch (Exception e) {
192 LOG.warn("Wizard error on cancel", e);
193 sendError(e);
194 ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
195 }
196 }
197
198 @Override
199 public final void onFinish() {
200 AjaxRequestTarget target = RequestCycle.get().find(AjaxRequestTarget.class).orElse(null);
201 try {
202 final Serializable res = onApply(target);
203 if (eventSink == null) {
204 send(this, Broadcast.BUBBLE, new NewItemFinishEvent<>(item, target).setResult(res));
205 } else {
206 send(eventSink, Broadcast.EXACT, new NewItemFinishEvent<>(item, target).setResult(res));
207 }
208 } catch (TimeoutException te) {
209 LOG.warn("Operation took too long", te);
210 if (eventSink == null) {
211 send(this, Broadcast.BUBBLE, new NewItemCancelEvent<>(item, target));
212 } else {
213 send(eventSink, Broadcast.EXACT, new NewItemCancelEvent<>(item, target));
214 }
215 sendWarning(getString("timeout"));
216 ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
217 } catch (CaptchaNotMatchingException ce) {
218 LOG.error("Wizard error on finish: captcha not matching", ce);
219 sendError(new WicketRuntimeException(getString(Constants.CAPTCHA_ERROR)));
220 ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
221 } catch (Exception e) {
222 LOG.error("Wizard error on finish", e);
223 sendError(e);
224 ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
225 }
226 }
227
228 @Override
229 public T getItem() {
230 return item;
231 }
232
233
234
235
236
237
238
239 public AjaxWizard<T> setItem(final T item) {
240 this.item = item;
241 return this;
242 }
243
244 @Override
245 public void onSubmit(final AjaxRequestTarget target) {
246 try {
247 onApply(target);
248 } catch (TimeoutException te) {
249 LOG.warn("Operation took too long", te);
250 send(eventSink, Broadcast.EXACT, new NewItemCancelEvent<>(item, target));
251 sendWarning(getString("timeout"));
252 ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
253 }
254 }
255
256 @Override
257 public void onError(final AjaxRequestTarget target) {
258 ((BaseWebPage) getPage()).getNotificationPanel().refresh(target);
259 }
260
261 private Serializable onApply(final AjaxRequestTarget target) throws TimeoutException {
262 try {
263 Future<Pair<Serializable, Serializable>> executor = execute(new ApplyFuture(target));
264
265 Pair<Serializable, Serializable> res = executor.get(getMaxWaitTimeInSeconds(), TimeUnit.SECONDS);
266
267 if (res.getLeft() != null) {
268 send(pageRef.getPage(), Broadcast.BUBBLE, res.getLeft());
269 }
270
271 return res.getRight();
272 } catch (InterruptedException | ExecutionException e) {
273 if (e.getCause() instanceof CaptchaNotMatchingException) {
274 throw (CaptchaNotMatchingException) e.getCause();
275 }
276 throw new WicketRuntimeException(e);
277 }
278 }
279
280 public abstract static class NewItemEvent<T extends Serializable> {
281
282 private final T item;
283
284 private IModel<String> titleModel;
285
286 private final AjaxRequestTarget target;
287
288 private WizardModalPanel<?> modalPanel;
289
290 public NewItemEvent(final T item, final AjaxRequestTarget target) {
291 this.item = item;
292 this.target = target;
293 }
294
295 public T getItem() {
296 return item;
297 }
298
299 public Optional<AjaxRequestTarget> getTarget() {
300 return Optional.ofNullable(target);
301 }
302
303 public WizardModalPanel<?> getModalPanel() {
304 return modalPanel;
305 }
306
307 public NewItemEvent<T> forceModalPanel(final WizardModalPanel<?> modalPanel) {
308 this.modalPanel = modalPanel;
309 return this;
310 }
311
312 public IModel<String> getTitleModel() {
313 return titleModel;
314 }
315
316 public NewItemEvent<T> setTitleModel(final IModel<String> titleModel) {
317 this.titleModel = titleModel;
318 return this;
319 }
320
321 public abstract String getEventDescription();
322 }
323
324 public static class NewItemActionEvent<T extends Serializable> extends NewItemEvent<T> {
325
326 private static final String EVENT_DESCRIPTION = "new";
327
328 private int index;
329
330 public NewItemActionEvent(final T item, final AjaxRequestTarget target) {
331 super(item, target);
332 }
333
334 public NewItemActionEvent(final T item, final int index, final AjaxRequestTarget target) {
335 super(item, target);
336 this.index = index;
337 }
338
339 public int getIndex() {
340 return index;
341 }
342
343 @Override
344 public String getEventDescription() {
345 return NewItemActionEvent.EVENT_DESCRIPTION;
346 }
347 }
348
349 public static class EditItemActionEvent<T extends Serializable> extends NewItemActionEvent<T> {
350
351 private static final String EVENT_DESCRIPTION = "edit";
352
353 public EditItemActionEvent(final T item, final AjaxRequestTarget target) {
354 super(item, target);
355 }
356
357 public EditItemActionEvent(final T item, final int index, final AjaxRequestTarget target) {
358 super(item, index, target);
359 }
360
361 @Override
362 public String getEventDescription() {
363 return EditItemActionEvent.EVENT_DESCRIPTION;
364 }
365 }
366
367 public static class NewItemCancelEvent<T extends Serializable> extends NewItemEvent<T> {
368
369 private static final String EVENT_DESCRIPTION = "cancel";
370
371 public NewItemCancelEvent(final T item, final AjaxRequestTarget target) {
372 super(item, target);
373 }
374
375 @Override
376 public String getEventDescription() {
377 return NewItemCancelEvent.EVENT_DESCRIPTION;
378 }
379 }
380
381 public static class NewItemFinishEvent<T extends Serializable> extends NewItemEvent<T> {
382
383 private static final String EVENT_DESCRIPTION = "finish";
384
385 private Serializable result;
386
387 public NewItemFinishEvent(final T item, final AjaxRequestTarget target) {
388 super(item, target);
389 }
390
391 @Override
392 public String getEventDescription() {
393 return NewItemFinishEvent.EVENT_DESCRIPTION;
394 }
395
396 public NewItemFinishEvent<T> setResult(final Serializable result) {
397 this.result = result;
398 return this;
399 }
400
401 public Serializable getResult() {
402 return result;
403 }
404 }
405
406 private class ApplyFuture implements Callable<Pair<Serializable, Serializable>>, Serializable {
407
408 private static final long serialVersionUID = -4657123322652656848L;
409
410 private final AjaxRequestTarget target;
411
412 private final Application application;
413
414 private final RequestCycle requestCycle;
415
416 private final Session session;
417
418 ApplyFuture(final AjaxRequestTarget target) {
419 this.target = target;
420 this.application = Application.get();
421 this.requestCycle = RequestCycle.get();
422 this.session = Session.exists() ? Session.get() : null;
423 }
424
425 @Override
426 public Pair<Serializable, Serializable> call() throws Exception {
427 try {
428 ThreadContext.setApplication(this.application);
429 ThreadContext.setRequestCycle(this.requestCycle);
430 ThreadContext.setSession(this.session);
431 return AjaxWizard.this.onApplyInternal(this.target);
432 } finally {
433 ThreadContext.detach();
434 }
435 }
436 }
437 }