You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2020/03/15 10:50:34 UTC

[syncope] branch master updated: [SYNCOPE-1531] Better busy management for CSV export

This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 37f44bf  [SYNCOPE-1531] Better busy management for CSV export
37f44bf is described below

commit 37f44bf5573a5d0c1c1f99e6e2658824a7fd0a3c
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Sun Mar 15 11:09:16 2020 +0100

    [SYNCOPE-1531] Better busy management for CSV export
---
 ...AnyDirectoryPanelAdditionalActionsProvider.java | 25 +++---
 .../console/wizards/CSVPushWizardBuilder.java      | 16 +++-
 .../client/ui/commons/HttpResourceStream.java      | 44 +++-------
 .../ResponseHolder.java}                           | 51 +++---------
 .../client/ui/commons/wizards/AjaxWizard.java      | 97 +++++++++++-----------
 .../ui/commons/wizards/AjaxWizardBuilder.java      | 19 +++--
 .../client/console/SerializableSupplier.java       | 26 ------
 .../syncope/client/console/pages/BasePage.java     |  5 +-
 .../console/reports/ReportExecutionDetails.java    | 21 ++---
 .../console/tasks/SchedTaskDirectoryPanel.java     | 14 +++-
 .../wicket/ajax/form/AjaxDownloadBehavior.java     | 19 ++---
 .../wicket/markup/html/form/BinaryFieldPanel.java  |  3 +-
 .../wicket/markup/html/link/VeilPopupSettings.java |  3 +-
 .../enduser/markup/html/form/BinaryFieldPanel.java |  4 +-
 .../enduser/wizards/any/AnyWizardBuilder.java      |  5 +-
 .../client/lib/SyncopeClientFactoryBean.java       |  1 +
 .../core/provisioning/java/VirAttrHandlerImpl.java |  2 +-
 .../client/console/panels/SAML2SPPanel.java        |  5 +-
 18 files changed, 155 insertions(+), 205 deletions(-)

diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMAnyDirectoryPanelAdditionalActionsProvider.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMAnyDirectoryPanelAdditionalActionsProvider.java
index 8118fc4..4d05f8f 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMAnyDirectoryPanelAdditionalActionsProvider.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMAnyDirectoryPanelAdditionalActionsProvider.java
@@ -88,12 +88,6 @@ public class IdMAnyDirectoryPanelAdditionalActionsProvider implements AnyDirecto
                     AjaxWizard.NewItemFinishEvent<?> payload = (AjaxWizard.NewItemFinishEvent) event.getPayload();
                     Optional<AjaxRequestTarget> target = payload.getTarget();
 
-                    SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
-                    if (target.isPresent()) {
-                        ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target.get());
-                        target.get().add(container);
-                    }
-
                     if (payload.getResult() instanceof ArrayList) {
                         modal.setContent(new ResultPage<Serializable>(
                                 null,
@@ -116,13 +110,20 @@ public class IdMAnyDirectoryPanelAdditionalActionsProvider implements AnyDirecto
                             }
                         });
                         target.ifPresent(t -> t.add(modal.getForm()));
+
+                        SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
                     } else if (Constants.OPERATION_SUCCEEDED.equals(payload.getResult())) {
-                        target.ifPresent(t -> {
-                            if (csvDownloadBehavior.hasResponse()) {
-                                csvDownloadBehavior.initiate(t);
-                            }
-                            modal.close(t);
-                        });
+                        target.ifPresent(modal::close);
+                        SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
+                    } else if (payload.getResult() instanceof Exception) {
+                        SyncopeConsoleSession.get().onException((Exception) payload.getResult());
+                    } else {
+                        SyncopeConsoleSession.get().error(payload.getResult());
+                    }
+
+                    if (target.isPresent()) {
+                        ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target.get());
+                        target.get().add(container);
                     }
                 }
             }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java
index ee2af97..2f07a4e 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java
@@ -29,6 +29,7 @@ import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
+import org.apache.syncope.client.ui.commons.rest.ResponseHolder;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.MatchingRule;
@@ -36,6 +37,7 @@ import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.beans.CSVPushSpec;
 import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.event.IEventSink;
 import org.apache.wicket.extensions.wizard.WizardModel;
 import org.apache.wicket.extensions.wizard.WizardStep;
@@ -43,6 +45,7 @@ import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.PropertyModel;
 import org.apache.wicket.model.util.ListModel;
+import org.apache.wicket.request.cycle.RequestCycle;
 
 public class CSVPushWizardBuilder extends BaseAjaxWizardBuilder<CSVPushSpec> {
 
@@ -71,8 +74,17 @@ public class CSVPushWizardBuilder extends BaseAjaxWizardBuilder<CSVPushSpec> {
 
     @Override
     protected Serializable onApplyInternal(final CSVPushSpec modelObject) {
-        downloadBehavior.setResponse(() -> ReconciliationRestClient.push(query, modelObject));
-        return Constants.OPERATION_SUCCEEDED;
+        return RequestCycle.get().find(AjaxRequestTarget.class).map(target -> {
+            try {
+                downloadBehavior.setResponse(new ResponseHolder(ReconciliationRestClient.push(query, modelObject)));
+                downloadBehavior.initiate(target);
+
+                return Constants.OPERATION_SUCCEEDED;
+            } catch (Exception e) {
+                LOG.error("While dowloading CSV export", e);
+                return e;
+            }
+        }).orElse(Constants.ERROR);
     }
 
     @Override
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/HttpResourceStream.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/HttpResourceStream.java
index eb08743..7bc8191 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/HttpResourceStream.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/HttpResourceStream.java
@@ -22,11 +22,8 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Optional;
-
-import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.ui.commons.rest.ResponseHolder;
 import org.apache.wicket.util.lang.Bytes;
 import org.apache.wicket.util.resource.AbstractResourceStream;
 import org.apache.wicket.util.resource.IFixedLocationResourceStream;
@@ -36,46 +33,25 @@ public class HttpResourceStream extends AbstractResourceStream implements IFixed
 
     private static final long serialVersionUID = 5811207817876330189L;
 
-    private transient InputStream inputStream;
-
-    private String contentType;
-
-    private String location;
+    private final ResponseHolder responseHolder;
 
-    private String filename;
-
-    public HttpResourceStream(final Response response) {
+    public HttpResourceStream(final ResponseHolder responseHolder) {
         super();
-
-        Object entity = response.getEntity();
-        if (response.getStatusInfo().getStatusCode() == Response.Status.OK.getStatusCode()
-                && (entity instanceof InputStream)) {
-
-            this.inputStream = (InputStream) entity;
-            this.contentType = response.getHeaderString(HttpHeaders.CONTENT_TYPE);
-            this.location = response.getLocation() == null ? null : response.getLocation().toASCIIString();
-            String contentDisposition = response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION);
-            if (StringUtils.isNotBlank(contentDisposition)) {
-                String[] splitted = contentDisposition.split("=");
-                if (splitted.length > 1) {
-                    this.filename = splitted[1].trim();
-                }
-            }
-        }
+        this.responseHolder = responseHolder;
     }
 
     @Override
     public InputStream getInputStream()
             throws ResourceStreamNotFoundException {
 
-        return inputStream == null
+        return responseHolder.getInputStream() == null
                 ? new ByteArrayInputStream(new byte[0])
-                : inputStream;
+                : responseHolder.getInputStream();
     }
 
     @Override
     public Bytes length() {
-        return inputStream == null
+        return responseHolder.getInputStream() == null
                 ? Bytes.bytes(0)
                 : null;
     }
@@ -87,15 +63,15 @@ public class HttpResourceStream extends AbstractResourceStream implements IFixed
 
     @Override
     public String locationAsString() {
-        return location;
+        return responseHolder.getLocation();
     }
 
     @Override
     public String getContentType() {
-        return Optional.ofNullable(contentType).orElse(MediaType.APPLICATION_OCTET_STREAM);
+        return Optional.ofNullable(responseHolder.getContentType()).orElse(MediaType.APPLICATION_OCTET_STREAM);
     }
 
     public String getFilename() {
-        return filename;
+        return responseHolder.getFilename();
     }
 }
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/HttpResourceStream.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/rest/ResponseHolder.java
similarity index 59%
copy from client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/HttpResourceStream.java
copy to client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/rest/ResponseHolder.java
index eb08743..8c8b186 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/HttpResourceStream.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/rest/ResponseHolder.java
@@ -16,25 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.ui.commons;
+package org.apache.syncope.client.ui.commons.rest;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.io.InputStream;
-import java.util.Optional;
-
+import java.io.Serializable;
 import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.wicket.util.lang.Bytes;
-import org.apache.wicket.util.resource.AbstractResourceStream;
-import org.apache.wicket.util.resource.IFixedLocationResourceStream;
-import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
 
-public class HttpResourceStream extends AbstractResourceStream implements IFixedLocationResourceStream {
+public class ResponseHolder implements Serializable {
 
-    private static final long serialVersionUID = 5811207817876330189L;
+    private static final long serialVersionUID = 2627155013246805827L;
 
     private transient InputStream inputStream;
 
@@ -44,9 +36,7 @@ public class HttpResourceStream extends AbstractResourceStream implements IFixed
 
     private String filename;
 
-    public HttpResourceStream(final Response response) {
-        super();
-
+    public ResponseHolder(final Response response) {
         Object entity = response.getEntity();
         if (response.getStatusInfo().getStatusCode() == Response.Status.OK.getStatusCode()
                 && (entity instanceof InputStream)) {
@@ -57,44 +47,25 @@ public class HttpResourceStream extends AbstractResourceStream implements IFixed
             String contentDisposition = response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION);
             if (StringUtils.isNotBlank(contentDisposition)) {
                 String[] splitted = contentDisposition.split("=");
-                if (splitted.length > 1) {
+                if (splitted != null && splitted.length > 1) {
                     this.filename = splitted[1].trim();
                 }
             }
         }
     }
 
-    @Override
-    public InputStream getInputStream()
-            throws ResourceStreamNotFoundException {
-
-        return inputStream == null
-                ? new ByteArrayInputStream(new byte[0])
-                : inputStream;
-    }
-
-    @Override
-    public Bytes length() {
-        return inputStream == null
-                ? Bytes.bytes(0)
-                : null;
+    public InputStream getInputStream() {
+        return inputStream;
     }
 
-    @Override
-    public void close() throws IOException {
-        // No need for explict closing
+    public String getContentType() {
+        return contentType;
     }
 
-    @Override
-    public String locationAsString() {
+    public String getLocation() {
         return location;
     }
 
-    @Override
-    public String getContentType() {
-        return Optional.ofNullable(contentType).orElse(MediaType.APPLICATION_OCTET_STREAM);
-    }
-
     public String getFilename() {
         return filename;
     }
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.java
index a0191aa..8b6edc3 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.java
@@ -77,19 +77,20 @@ public abstract class AjaxWizard<T extends Serializable> extends Wizard
 
     private final Mode mode;
 
+    private final boolean async;
+
     private IEventSink eventSink;
 
     private final PageReference pageRef;
 
-    private AjaxWizardMgtButtonBar<T> buttonBar;
-
     /**
      * Construct.
      *
-     * @param id The component id.
-     * @param item model object.
+     * @param id The component id
+     * @param item model object
      * @param model wizard model
-     * @param mode {@code true} if edit mode.
+     * @param mode mode
+     * @param async should apply go async or not?
      * @param pageRef caller page reference.
      */
     public AjaxWizard(
@@ -97,10 +98,13 @@ public abstract class AjaxWizard<T extends Serializable> extends Wizard
             final T item,
             final WizardModel model,
             final Mode mode,
+            final boolean async,
             final PageReference pageRef) {
+
         super(id);
         this.item = item;
         this.mode = mode;
+        this.async = async;
         this.pageRef = pageRef;
 
         if (mode == Mode.READONLY) {
@@ -147,7 +151,7 @@ public abstract class AjaxWizard<T extends Serializable> extends Wizard
         getForm().remove(FEEDBACK_ID);
 
         if (mode == Mode.READONLY) {
-            final Iterator<IWizardStep> iter = wizardModel.stepIterator();
+            Iterator<IWizardStep> iter = wizardModel.stepIterator();
             while (iter.hasNext()) {
                 WizardStep.class.cast(iter.next()).setEnabled(false);
             }
@@ -156,12 +160,7 @@ public abstract class AjaxWizard<T extends Serializable> extends Wizard
 
     @Override
     protected Component newButtonBar(final String id) {
-        this.buttonBar = new AjaxWizardMgtButtonBar<>(id, this, mode);
-        return this.buttonBar;
-    }
-
-    public AjaxWizardMgtButtonBar<T> getButtonBar() {
-        return buttonBar;
+        return new AjaxWizardMgtButtonBar<>(id, this, mode);
     }
 
     protected abstract void onCancelInternal();
@@ -246,6 +245,43 @@ public abstract class AjaxWizard<T extends Serializable> extends Wizard
         return this;
     }
 
+    @Override
+    public void onSubmit(final AjaxRequestTarget target) {
+        try {
+            onApply(target);
+        } catch (TimeoutException te) {
+            LOG.warn("Operation took too long", te);
+            send(eventSink, Broadcast.EXACT, new NewItemCancelEvent<>(item, target));
+            sendWarning(getString("timeout"));
+            ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
+        }
+    }
+
+    @Override
+    public void onError(final AjaxRequestTarget target) {
+        ((BaseWebPage) getPage()).getNotificationPanel().refresh(target);
+    }
+
+    private Serializable onApply(final AjaxRequestTarget target) throws TimeoutException {
+        try {
+            Future<Pair<Serializable, Serializable>> executor = execute(new ApplyFuture(target));
+
+            Pair<Serializable, Serializable> res =
+                    executor.get(getMaxWaitTimeInSeconds(), TimeUnit.SECONDS);
+
+            if (res.getLeft() != null) {
+                send(pageRef.getPage(), Broadcast.BUBBLE, res.getLeft());
+            }
+
+            return res.getRight();
+        } catch (InterruptedException | ExecutionException e) {
+            if (e.getCause() instanceof CaptchaNotMatchingException) {
+                throw (CaptchaNotMatchingException) e.getCause();
+            }
+            throw new WicketRuntimeException(e);
+        }
+    }
+
     public abstract static class NewItemEvent<T extends Serializable> {
 
         private final T item;
@@ -372,43 +408,6 @@ public abstract class AjaxWizard<T extends Serializable> extends Wizard
         }
     }
 
-    @Override
-    public void onSubmit(final AjaxRequestTarget target) {
-        try {
-            onApply(target);
-        } catch (TimeoutException te) {
-            LOG.warn("Operation took too long", te);
-            send(eventSink, Broadcast.EXACT, new NewItemCancelEvent<>(item, target));
-            sendWarning(getString("timeout"));
-            ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
-        }
-    }
-
-    @Override
-    public void onError(final AjaxRequestTarget target) {
-        ((BaseWebPage) getPage()).getNotificationPanel().refresh(target);
-    }
-
-    private Serializable onApply(final AjaxRequestTarget target) throws TimeoutException {
-        try {
-            Future<Pair<Serializable, Serializable>> executor = execute(new ApplyFuture(target));
-
-            Pair<Serializable, Serializable> res =
-                    executor.get(getMaxWaitTimeInSeconds(), TimeUnit.SECONDS);
-
-            if (res.getLeft() != null) {
-                send(pageRef.getPage(), Broadcast.BUBBLE, res.getLeft());
-            }
-
-            return res.getRight();
-        } catch (InterruptedException | ExecutionException e) {
-            if (e.getCause() instanceof CaptchaNotMatchingException) {
-                throw (CaptchaNotMatchingException) e.getCause();
-            }
-            throw new WicketRuntimeException(e);
-        }
-    }
-
     private class ApplyFuture implements Callable<Pair<Serializable, Serializable>>, Serializable {
 
         private static final long serialVersionUID = -4657123322652656848L;
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizardBuilder.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizardBuilder.java
index ea517e4..5c8575a 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizardBuilder.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizardBuilder.java
@@ -43,6 +43,8 @@ public abstract class AjaxWizardBuilder<T extends Serializable> extends Abstract
 
     protected final List<Component> outerObjects = new ArrayList<>();
 
+    private boolean async = true;
+
     /**
      * Construct.
      *
@@ -53,14 +55,19 @@ public abstract class AjaxWizardBuilder<T extends Serializable> extends Abstract
         super(defaultItem, pageRef);
     }
 
-    public final AjaxWizardBuilder<T> addOuterObject(final Component... childs) {
+    public AjaxWizardBuilder<T> setAsync(final boolean async) {
+        this.async = async;
+        return this;
+    }
+
+    public AjaxWizardBuilder<T> addOuterObject(final Component... childs) {
         outerObjects.addAll(List.of(childs));
         return this;
     }
 
     @Override
     public AjaxWizard<T> build(final String id, final int index, final AjaxWizard.Mode mode) {
-        final AjaxWizard<T> wizard = build(id, mode);
+        AjaxWizard<T> wizard = build(id, mode);
         for (int i = 1; i < index; i++) {
             wizard.getWizardModel().next();
         }
@@ -88,20 +95,20 @@ public abstract class AjaxWizardBuilder<T extends Serializable> extends Abstract
         this.mode = mode;
 
         // get the specified item if available
-        final T modelObject = newModelObject();
+        T modelObj = newModelObject();
 
-        return new AjaxWizard<T>(id, modelObject, buildModelSteps(modelObject, new WizardModel()), mode, pageRef) {
+        return new AjaxWizard<T>(id, modelObj, buildModelSteps(modelObj, new WizardModel()), mode, async, pageRef) {
 
             private static final long serialVersionUID = 7770507663760640735L;
 
             @Override
             protected void onCancelInternal() {
-                AjaxWizardBuilder.this.onCancelInternal(modelObject);
+                AjaxWizardBuilder.this.onCancelInternal(modelObj);
             }
 
             @Override
             protected Pair<Serializable, Serializable> onApplyInternal(final AjaxRequestTarget target) {
-                Serializable res = AjaxWizardBuilder.this.onApplyInternal(modelObject);
+                Serializable res = AjaxWizardBuilder.this.onApplyInternal(modelObj);
 
                 Serializable payload;
                 switch (mode) {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SerializableSupplier.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SerializableSupplier.java
deleted file mode 100644
index da3eb79..0000000
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SerializableSupplier.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.client.console;
-
-import java.io.Serializable;
-import java.util.function.Supplier;
-
-@FunctionalInterface
-public interface SerializableSupplier<T> extends Supplier<T>, Serializable {
-}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
index 324769b..d2c1e32 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
@@ -35,6 +35,7 @@ import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.Bas
 import org.apache.syncope.client.console.widgets.ExtAlertWidget;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
+import org.apache.syncope.client.ui.commons.rest.ResponseHolder;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.info.SystemInfo;
@@ -121,8 +122,8 @@ public class BasePage extends BaseWebPage {
             @Override
             public void onClick() {
                 try {
-                    HttpResourceStream stream =
-                            new HttpResourceStream(SyncopeRestClient.exportInternalStorageContent());
+                    HttpResourceStream stream = new HttpResourceStream(
+                            new ResponseHolder(SyncopeRestClient.exportInternalStorageContent()));
 
                     ResourceStreamRequestHandler rsrh = new ResourceStreamRequestHandler(stream);
                     rsrh.setFileName(stream.getFilename() == null
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java
index 2009bbb..5d820e0 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java
@@ -25,6 +25,7 @@ import org.apache.syncope.client.console.tasks.ExecutionsDirectoryPanel;
 import org.apache.syncope.client.console.wicket.ajax.form.AjaxDownloadBehavior;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.ui.commons.rest.ResponseHolder;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.ReportTO;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
@@ -85,8 +86,8 @@ public class ReportExecutionDetails extends MultilevelPanel.SecondLevel {
 
                 @Override
                 public void onClick(final AjaxRequestTarget target, final ExecTO ignore) {
-                    downloadBehavior.setResponse(() -> ReportRestClient.exportExecutionResult(
-                            model.getObject().getKey(), ReportExecExportFormat.CSV));
+                    downloadBehavior.setResponse(new ResponseHolder(ReportRestClient.exportExecutionResult(
+                            model.getObject().getKey(), ReportExecExportFormat.CSV)));
                     downloadBehavior.initiate(target);
                 }
             }, ActionLink.ActionType.EXPORT_CSV, IdRepoEntitlement.REPORT_READ);
@@ -97,8 +98,8 @@ public class ReportExecutionDetails extends MultilevelPanel.SecondLevel {
 
                 @Override
                 public void onClick(final AjaxRequestTarget target, final ExecTO ignore) {
-                    downloadBehavior.setResponse(() -> ReportRestClient.exportExecutionResult(
-                            model.getObject().getKey(), ReportExecExportFormat.HTML));
+                    downloadBehavior.setResponse(new ResponseHolder(ReportRestClient.exportExecutionResult(
+                            model.getObject().getKey(), ReportExecExportFormat.HTML)));
                     downloadBehavior.initiate(target);
                 }
             }, ActionLink.ActionType.EXPORT_HTML, IdRepoEntitlement.REPORT_READ);
@@ -109,8 +110,8 @@ public class ReportExecutionDetails extends MultilevelPanel.SecondLevel {
 
                 @Override
                 public void onClick(final AjaxRequestTarget target, final ExecTO ignore) {
-                    downloadBehavior.setResponse(() -> ReportRestClient.exportExecutionResult(
-                            model.getObject().getKey(), ReportExecExportFormat.PDF));
+                    downloadBehavior.setResponse(new ResponseHolder(ReportRestClient.exportExecutionResult(
+                            model.getObject().getKey(), ReportExecExportFormat.PDF)));
                     downloadBehavior.initiate(target);
                 }
             }, ActionLink.ActionType.EXPORT_PDF, IdRepoEntitlement.REPORT_READ);
@@ -121,8 +122,8 @@ public class ReportExecutionDetails extends MultilevelPanel.SecondLevel {
 
                 @Override
                 public void onClick(final AjaxRequestTarget target, final ExecTO ignore) {
-                    downloadBehavior.setResponse(() -> ReportRestClient.exportExecutionResult(
-                            model.getObject().getKey(), ReportExecExportFormat.RTF));
+                    downloadBehavior.setResponse(new ResponseHolder(ReportRestClient.exportExecutionResult(
+                            model.getObject().getKey(), ReportExecExportFormat.RTF)));
                     downloadBehavior.initiate(target);
                 }
             }, ActionLink.ActionType.EXPORT_RTF, IdRepoEntitlement.REPORT_READ);
@@ -133,8 +134,8 @@ public class ReportExecutionDetails extends MultilevelPanel.SecondLevel {
 
                 @Override
                 public void onClick(final AjaxRequestTarget target, final ExecTO ignore) {
-                    downloadBehavior.setResponse(() -> ReportRestClient.exportExecutionResult(
-                            model.getObject().getKey(), ReportExecExportFormat.XML));
+                    downloadBehavior.setResponse(new ResponseHolder(ReportRestClient.exportExecutionResult(
+                            model.getObject().getKey(), ReportExecExportFormat.XML)));
                     downloadBehavior.initiate(target);
                 }
             }, ActionLink.ActionType.EXPORT_XML, IdRepoEntitlement.REPORT_READ);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
index 2cfa4a4..ff0fd28 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
@@ -32,6 +32,7 @@ import org.apache.syncope.client.console.commons.TaskDataProvider;
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.rest.TaskRestClient;
+import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn;
@@ -65,6 +66,7 @@ import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.util.time.Duration;
 
 /**
  * Tasks page.
@@ -112,6 +114,17 @@ public abstract class SchedTaskDirectoryPanel<T extends SchedTaskTO>
 
         initResultTable();
 
+        container.add(new IndicatorAjaxTimerBehavior(Duration.seconds(10)) {
+
+            private static final long serialVersionUID = -4661303265651934868L;
+
+            @Override
+            protected void onTimer(final AjaxRequestTarget target) {
+                container.modelChanged();
+                target.add(container);
+            }
+        });
+
         startAt = new TaskStartAtTogglePanel(container, pageRef);
         addInnerObject(startAt);
 
@@ -128,7 +141,6 @@ public abstract class SchedTaskDirectoryPanel<T extends SchedTaskTO>
                 return targetObject;
             }
         };
-
         addInnerObject(templates);
     }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AjaxDownloadBehavior.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AjaxDownloadBehavior.java
index 01cf3ec..3a38e91 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AjaxDownloadBehavior.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AjaxDownloadBehavior.java
@@ -18,9 +18,8 @@
  */
 package org.apache.syncope.client.console.wicket.ajax.form;
 
-import javax.ws.rs.core.Response;
-import org.apache.syncope.client.console.SerializableSupplier;
 import org.apache.syncope.client.ui.commons.HttpResourceStream;
+import org.apache.syncope.client.ui.commons.rest.ResponseHolder;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.behavior.AbstractAjaxBehavior;
 import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
@@ -34,14 +33,10 @@ public class AjaxDownloadBehavior extends AbstractAjaxBehavior {
 
     protected static final Logger LOG = LoggerFactory.getLogger(AjaxDownloadBehavior.class);
 
-    protected SerializableSupplier<Response> response;
+    protected ResponseHolder responseHolder;
 
-    public boolean hasResponse() {
-        return response != null;
-    }
-
-    public void setResponse(final SerializableSupplier<Response> response) {
-        this.response = response;
+    public void setResponse(final ResponseHolder response) {
+        this.responseHolder = response;
     }
 
     /**
@@ -57,9 +52,9 @@ public class AjaxDownloadBehavior extends AbstractAjaxBehavior {
     protected HttpResourceStream getResourceStream() {
         HttpResourceStream stream = null;
 
-        if (response != null) {
-            stream = new HttpResourceStream(response.get());
-            response = null;
+        if (responseHolder != null) {
+            stream = new HttpResourceStream(responseHolder);
+            responseHolder = null;
         }
 
         return stream;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java
index 5ce72d7..ee4ff43 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java
@@ -41,6 +41,7 @@ import org.apache.syncope.client.ui.commons.HttpResourceStream;
 import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.preview.AbstractBinaryPreviewer;
 import org.apache.syncope.client.ui.commons.markup.html.form.BaseBinaryFieldPanel;
+import org.apache.syncope.client.ui.commons.rest.ResponseHolder;
 import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
@@ -155,7 +156,7 @@ public class BinaryFieldPanel extends BaseBinaryFieldPanel {
 
             @Override
             protected HttpResourceStream getResourceStream() {
-                return new HttpResourceStream(buildResponse());
+                return new HttpResourceStream(new ResponseHolder(buildResponse()));
             }
         };
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/link/VeilPopupSettings.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/link/VeilPopupSettings.java
index e7ce827..418e190 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/link/VeilPopupSettings.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/link/VeilPopupSettings.java
@@ -35,7 +35,6 @@ public class VeilPopupSettings extends PopupSettings {
                 + "    window.location.reload(false);"
                 + "  }"
                 + "}, 1000);"
-                + "return false";
+                + "return false;";
     }
-
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.java
index e7a629e..5343661 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.java
@@ -40,6 +40,7 @@ import org.apache.syncope.client.ui.commons.markup.html.form.BaseBinaryFieldPane
 import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.preview.AbstractBinaryPreviewer;
 import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
+import org.apache.syncope.client.ui.commons.rest.ResponseHolder;
 import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
@@ -153,9 +154,8 @@ public class BinaryFieldPanel extends BaseBinaryFieldPanel {
 
             @Override
             protected HttpResourceStream getResourceStream() {
-                return new HttpResourceStream(buildResponse());
+                return new HttpResourceStream(new ResponseHolder(buildResponse()));
             }
-
         };
 
         add(fileDownload);
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AnyWizardBuilder.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AnyWizardBuilder.java
index 13c3339..a78560b 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AnyWizardBuilder.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AnyWizardBuilder.java
@@ -172,8 +172,8 @@ public abstract class AnyWizardBuilder extends AbstractAnyWizardBuilder<UserTO>
         // get the specified item if available
         final AnyWrapper<UserTO> modelObject = newModelObject();
 
-        return new AjaxWizard<AnyWrapper<UserTO>>(id, modelObject, buildModelSteps(modelObject, new WizardModel()),
-                mode, this.pageRef) {
+        return new AjaxWizard<AnyWrapper<UserTO>>(
+                id, modelObject, buildModelSteps(modelObject, new WizardModel()), mode, true, this.pageRef) {
 
             private static final long serialVersionUID = 7770507663760640735L;
 
@@ -294,5 +294,4 @@ public abstract class AnyWizardBuilder extends AbstractAnyWizardBuilder<UserTO>
 
         }.setEventSink(eventSink).addOuterObject(outerObjects);
     }
-
 }
diff --git a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
index 7244a4d..572bc1a 100644
--- a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
+++ b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
@@ -228,6 +228,7 @@ public class SyncopeClientFactoryBean {
      * Sets the client TLS configuration.
      *
      * @param tlsClientParameters client TLS configuration
+     * @return the current instance
      */
     public SyncopeClientFactoryBean setTlsClientParameters(final TLSClientParameters tlsClientParameters) {
         this.tlsClientParameters = tlsClientParameters;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java
index 11cdc39..5e6787a 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java
@@ -66,7 +66,7 @@ public class VirAttrHandlerImpl implements VirAttrHandler {
     @Override
     public void setValues(final Any<?> any, final ConnectorObject connObj) {
         if (any == null) {
-            LOG.warn("Null any passed, ignoring");
+            LOG.debug("Null any passed, ignoring");
             return;
         }
 
diff --git a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2SPPanel.java b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2SPPanel.java
index 81bfeef..1cfe352 100644
--- a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2SPPanel.java
+++ b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2SPPanel.java
@@ -21,6 +21,7 @@ package org.apache.syncope.client.console.panels;
 import javax.ws.rs.client.ClientBuilder;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.ui.commons.HttpResourceStream;
+import org.apache.syncope.client.ui.commons.rest.ResponseHolder;
 import org.apache.wicket.markup.html.link.Link;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.request.Url;
@@ -47,10 +48,10 @@ public class SAML2SPPanel extends Panel {
             @Override
             public void onClick() {
                 try {
-                    HttpResourceStream stream = new HttpResourceStream(ClientBuilder.newClient().
+                    HttpResourceStream stream = new HttpResourceStream(new ResponseHolder(ClientBuilder.newClient().
                             target(RequestCycle.get().getUrlRenderer().renderFullUrl(Url.parse(
                                     UrlUtils.rewriteToContextRelative("saml2sp/metadata", RequestCycle.get())))).
-                            request().get());
+                            request().get()));
 
                     ResourceStreamRequestHandler rsrh = new ResourceStreamRequestHandler(stream);
                     rsrh.setFileName(stream.getFilename() == null