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:26:47 UTC

[syncope] branch 2_1_X 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 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


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

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

    [SYNCOPE-1531] Better busy management for CSV export
---
 client/console/pom.xml                             |  8 ++
 .../client/console/SerializableSupplier.java       | 26 ------
 .../client/console/commons/HttpResourceStream.java | 45 +++-------
 .../syncope/client/console/pages/BasePage.java     |  4 +-
 .../client/console/panels/AnyDirectoryPanel.java   | 27 +++---
 .../console/reports/ReportExecutionDetails.java    | 21 ++---
 .../ResponseHolder.java}                           | 49 +++--------
 .../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 +-
 .../syncope/client/console/wizards/AjaxWizard.java | 97 +++++++++++-----------
 .../client/console/wizards/AjaxWizardBuilder.java  | 19 +++--
 .../console/wizards/CSVPushWizardBuilder.java      | 16 +++-
 .../client/lib/SyncopeClientFactoryBean.java       |  1 +
 .../core/provisioning/java/VirAttrHandlerImpl.java |  2 +-
 16 files changed, 159 insertions(+), 195 deletions(-)

diff --git a/client/console/pom.xml b/client/console/pom.xml
index e6b6b70..2867882 100644
--- a/client/console/pom.xml
+++ b/client/console/pom.xml
@@ -191,6 +191,14 @@ under the License.
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
       </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.3.2</version>
+        <configuration>
+          <debug>true</debug>
+        </configuration>
+      </plugin>
     </plugins>
     
     <resources>
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/SerializableSupplier.java b/client/console/src/main/java/org/apache/syncope/client/console/SerializableSupplier.java
deleted file mode 100644
index da3eb79..0000000
--- a/client/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/console/src/main/java/org/apache/syncope/client/console/commons/HttpResourceStream.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/HttpResourceStream.java
index feeba85..9fcb85a 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/commons/HttpResourceStream.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/HttpResourceStream.java
@@ -21,10 +21,8 @@ package org.apache.syncope.client.console.commons;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-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.console.rest.ResponseHolder;
 import org.apache.wicket.util.lang.Bytes;
 import org.apache.wicket.util.resource.AbstractResourceStream;
 import org.apache.wicket.util.resource.IFixedLocationResourceStream;
@@ -34,46 +32,25 @@ public class HttpResourceStream extends AbstractResourceStream implements IFixed
 
     private static final long serialVersionUID = 5811207817876330189L;
 
-    private transient InputStream inputStream;
+    private final ResponseHolder responseHolder;
 
-    private String contentType;
-
-    private String location;
-
-    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 != null && 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;
     }
@@ -85,17 +62,17 @@ public class HttpResourceStream extends AbstractResourceStream implements IFixed
 
     @Override
     public String locationAsString() {
-        return location;
+        return responseHolder.getLocation();
     }
 
     @Override
     public String getContentType() {
-        return contentType == null
+        return responseHolder.getContentType() == null
                 ? MediaType.APPLICATION_OCTET_STREAM
-                : contentType;
+                : responseHolder.getContentType();
     }
 
     public String getFilename() {
-        return filename;
+        return responseHolder.getFilename();
     }
 }
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
index f343dd7..cd92494 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
@@ -32,6 +32,7 @@ import org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.console.init.ConsoleInitializer;
 import org.apache.syncope.client.console.panels.NotificationPanel;
 import org.apache.syncope.client.console.rest.ConfRestClient;
+import org.apache.syncope.client.console.rest.ResponseHolder;
 import org.apache.syncope.client.console.topology.Topology;
 import org.apache.syncope.client.console.wicket.markup.head.MetaHeaderItem;
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
@@ -132,7 +133,8 @@ public class BasePage extends WebPage implements IAjaxIndicatorAware {
             @Override
             public void onClick() {
                 try {
-                    HttpResourceStream stream = new HttpResourceStream(new ConfRestClient().dbExport());
+                    HttpResourceStream stream = new HttpResourceStream(
+                            new ResponseHolder(new ConfRestClient().dbExport()));
 
                     ResourceStreamRequestHandler rsrh = new ResourceStreamRequestHandler(stream);
                     rsrh.setFileName(stream.getFilename() == null
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java
index 4b2375e..739705b 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java
@@ -175,12 +175,6 @@ public abstract class AnyDirectoryPanel<A extends AnyTO, E extends AbstractAnyRe
                     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,
@@ -203,13 +197,20 @@ public abstract class AnyDirectoryPanel<A extends AnyTO, E extends AbstractAnyRe
                             }
                         });
                         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);
                     }
                 }
             }
@@ -226,7 +227,7 @@ public abstract class AnyDirectoryPanel<A extends AnyTO, E extends AbstractAnyRe
                 AnyQuery query = csvAnyQuery();
 
                 target.add(modal.setContent(new CSVPushWizardBuilder(spec, query, csvDownloadBehavior, pageRef).
-                        setEventSink(csvEventSink).
+                        setEventSink(csvEventSink).setAsync(false).
                         build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT)));
 
                 modal.header(new StringResourceModel("csvPush", AnyDirectoryPanel.this, Model.of(spec)));
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java
index 013e967..b087658 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java
@@ -21,6 +21,7 @@ package org.apache.syncope.client.console.reports;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.rest.ExecutionRestClient;
 import org.apache.syncope.client.console.rest.ReportRestClient;
+import org.apache.syncope.client.console.rest.ResponseHolder;
 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;
@@ -87,8 +88,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, StandardEntitlement.REPORT_READ);
@@ -99,8 +100,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, StandardEntitlement.REPORT_READ);
@@ -111,8 +112,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, StandardEntitlement.REPORT_READ);
@@ -123,8 +124,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, StandardEntitlement.REPORT_READ);
@@ -135,8 +136,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, StandardEntitlement.REPORT_READ);
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/HttpResourceStream.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResponseHolder.java
similarity index 61%
copy from client/console/src/main/java/org/apache/syncope/client/console/commons/HttpResourceStream.java
copy to client/console/src/main/java/org/apache/syncope/client/console/rest/ResponseHolder.java
index feeba85..2311a59 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/commons/HttpResourceStream.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResponseHolder.java
@@ -16,23 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.commons;
+package org.apache.syncope.client.console.rest;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.io.InputStream;
+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;
 
@@ -42,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)) {
@@ -62,39 +54,18 @@ public class HttpResourceStream extends AbstractResourceStream implements IFixed
         }
     }
 
-    @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 contentType == null
-                ? MediaType.APPLICATION_OCTET_STREAM
-                : contentType;
-    }
-
     public String getFilename() {
         return filename;
     }
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
index eba0553..0e0e335 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
@@ -32,6 +32,7 @@ import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.panels.ModalPanel;
 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;
@@ -64,6 +65,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.
@@ -111,6 +113,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);
 
@@ -127,7 +140,6 @@ public abstract class SchedTaskDirectoryPanel<T extends SchedTaskTO>
                 return targetObject;
             }
         };
-
         addInnerObject(templates);
     }
 
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AjaxDownloadBehavior.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AjaxDownloadBehavior.java
index a2c942d..acf6045 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AjaxDownloadBehavior.java
+++ b/client/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.console.commons.HttpResourceStream;
+import org.apache.syncope.client.console.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/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java
index da4b2dd..610ac5b 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java
@@ -38,6 +38,7 @@ import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.commons.HttpResourceStream;
 import org.apache.syncope.client.console.commons.PreviewUtils;
 import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.rest.ResponseHolder;
 import org.apache.syncope.client.console.wicket.markup.html.form.preview.AbstractBinaryPreviewer;
 import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -150,7 +151,7 @@ public class BinaryFieldPanel extends FieldPanel<String> {
 
             @Override
             protected HttpResourceStream getResourceStream() {
-                return new HttpResourceStream(buildResponse());
+                return new HttpResourceStream(new ResponseHolder(buildResponse()));
             }
         };
 
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/link/VeilPopupSettings.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/link/VeilPopupSettings.java
index c85cfda..c0e8ff0 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/link/VeilPopupSettings.java
+++ b/client/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/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizard.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizard.java
index 7485492..f756c9d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizard.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/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 <tt>true</tt> 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();
@@ -239,6 +238,45 @@ 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));
+            SyncopeConsoleSession.get().warn(getString("timeout"));
+            ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+        }
+    }
+
+    @Override
+    public void onError(final AjaxRequestTarget target) {
+        ((BasePage) getPage()).getNotificationPanel().refresh(target);
+    }
+
+    private Serializable onApply(final AjaxRequestTarget target) throws TimeoutException {
+        Pair<Serializable, Serializable> results;
+        if (async) {
+            try {
+                Future<Pair<Serializable, Serializable>> future =
+                        SyncopeConsoleSession.get().execute(new ApplyFuture(target));
+
+                results = future.get(SyncopeConsoleApplication.get().getMaxWaitTimeInSeconds(), TimeUnit.SECONDS);
+            } catch (InterruptedException | ExecutionException e) {
+                throw new WicketRuntimeException(e);
+            }
+        } else {
+            results = onApplyInternal(target);
+        }
+
+        if (results.getLeft() != null) {
+            send(pageRef.getPage(), Broadcast.BUBBLE, results.getLeft());
+        }
+
+        return results.getRight();
+    }
+
     public abstract static class NewItemEvent<T extends Serializable> {
 
         private final T item;
@@ -365,41 +403,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));
-            SyncopeConsoleSession.get().warn(getString("timeout"));
-            ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
-        }
-    }
-
-    @Override
-    public void onError(final AjaxRequestTarget target) {
-        ((BasePage) getPage()).getNotificationPanel().refresh(target);
-    }
-
-    private Serializable onApply(final AjaxRequestTarget target) throws TimeoutException {
-        try {
-            Future<Pair<Serializable, Serializable>> executor =
-                    SyncopeConsoleSession.get().execute(new ApplyFuture(target));
-
-            Pair<Serializable, Serializable> res =
-                    executor.get(SyncopeConsoleApplication.get().getMaxWaitTimeInSeconds(), TimeUnit.SECONDS);
-
-            if (res.getLeft() != null) {
-                send(pageRef.getPage(), Broadcast.BUBBLE, res.getLeft());
-            }
-
-            return res.getRight();
-        } catch (InterruptedException | ExecutionException e) {
-            throw new WicketRuntimeException(e);
-        }
-    }
-
     private class ApplyFuture implements Callable<Pair<Serializable, Serializable>>, Serializable {
 
         private static final long serialVersionUID = -4657123322652656848L;
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java
index 27d2c79..6f1d01f 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java
@@ -40,6 +40,8 @@ public abstract class AjaxWizardBuilder<T extends Serializable> extends Abstract
 
     private final List<Component> outerObjects = new ArrayList<>();
 
+    private boolean async = true;
+
     /**
      * Construct.
      *
@@ -50,7 +52,12 @@ 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(Arrays.asList(childs));
         return this;
     }
@@ -60,7 +67,7 @@ public abstract class AjaxWizardBuilder<T extends Serializable> extends Abstract
      */
     @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/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java
index cfc72e0..8fa1865 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java
@@ -26,6 +26,7 @@ import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.panels.CSVConfPanel;
 import org.apache.syncope.client.console.rest.ImplementationRestClient;
 import org.apache.syncope.client.console.rest.ReconciliationRestClient;
+import org.apache.syncope.client.console.rest.ResponseHolder;
 import org.apache.syncope.client.console.wicket.ajax.form.AjaxDownloadBehavior;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
@@ -37,6 +38,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;
@@ -44,6 +46,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 AjaxWizardBuilder<CSVPushSpec> {
 
@@ -74,8 +77,17 @@ public class CSVPushWizardBuilder extends AjaxWizardBuilder<CSVPushSpec> {
 
     @Override
     protected Serializable onApplyInternal(final CSVPushSpec modelObject) {
-        downloadBehavior.setResponse(() -> restClient.push(query, modelObject));
-        return Constants.OPERATION_SUCCEEDED;
+        return RequestCycle.get().find(AjaxRequestTarget.class).map(target -> {
+            try {
+                downloadBehavior.setResponse(new ResponseHolder(restClient.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/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
index f34c5f8..40d3b5d 100644
--- a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
@@ -233,6 +233,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 6fd27ad..cc15730 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
@@ -67,7 +67,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;
         }