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 2018/03/02 11:47:20 UTC

[01/11] syncope git commit: Small improvements to build-tools, including Swagger

Repository: syncope
Updated Branches:
  refs/heads/2_0_X de3696469 -> 799f079f1
  refs/heads/master 2d2702a0a -> 772206a4c


Small improvements to build-tools, including Swagger


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/3e2ebb92
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/3e2ebb92
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/3e2ebb92

Branch: refs/heads/2_0_X
Commit: 3e2ebb925774d3542e40d54983395ec785f4710a
Parents: de36964
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Thu Mar 1 15:47:13 2018 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Thu Mar 1 15:47:13 2018 +0100

----------------------------------------------------------------------
 fit/build-tools/pom.xml                         | 16 +++++++++++++++-
 .../syncope/fit/buildtools/cxf/UserService.java |  3 ++-
 .../fit/buildtools/cxf/UserServiceImpl.java     | 13 +++++++++++--
 .../META-INF/cxf/org.apache.cxf.Logger          |  1 +
 .../src/main/resources/cxfContext.xml           | 20 ++++++++++++++++++++
 5 files changed, 49 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/3e2ebb92/fit/build-tools/pom.xml
----------------------------------------------------------------------
diff --git a/fit/build-tools/pom.xml b/fit/build-tools/pom.xml
index 4c63a9c..95f2365 100644
--- a/fit/build-tools/pom.xml
+++ b/fit/build-tools/pom.xml
@@ -45,6 +45,11 @@ under the License.
     </dependency>
     
     <dependency>
+      <groupId>javax.validation</groupId>
+      <artifactId>validation-api</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.directory.server</groupId>
       <artifactId>apacheds-all</artifactId>
     </dependency>
@@ -88,13 +93,22 @@ under the License.
     <dependency>
       <groupId>org.apache.cxf</groupId>
       <artifactId>cxf-rt-rs-service-description</artifactId>
-    </dependency>    
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-service-description-swagger</artifactId>
+    </dependency>
     <dependency>
       <groupId>com.fasterxml.jackson.jaxrs</groupId>
       <artifactId>jackson-jaxrs-json-provider</artifactId>
     </dependency>
 
     <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>swagger-ui</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-core</artifactId>
       <version>${spring.version}</version>

http://git-wip-us.apache.org/repos/asf/syncope/blob/3e2ebb92/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java
----------------------------------------------------------------------
diff --git a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java
index 5d64cfc..f02cae4 100644
--- a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java
+++ b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java
@@ -30,6 +30,7 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
 
 @Path("users")
 public interface UserService {
@@ -44,7 +45,7 @@ public interface UserService {
 
     @POST
     @Consumes({ MediaType.APPLICATION_JSON })
-    void create(User user);
+    Response create(User user);
 
     @PUT
     @Path("{key}")

http://git-wip-us.apache.org/repos/asf/syncope/blob/3e2ebb92/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java
----------------------------------------------------------------------
diff --git a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java
index dea0f63..6b04515 100644
--- a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java
+++ b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java
@@ -23,8 +23,12 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import javax.ws.rs.ClientErrorException;
 import javax.ws.rs.ForbiddenException;
 import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -32,6 +36,9 @@ public class UserServiceImpl implements UserService {
 
     private static final Map<UUID, User> USERS = new HashMap<UUID, User>();
 
+    @Context
+    private UriInfo uriInfo;
+
     @Override
     public List<User> list() {
         return new ArrayList<>(USERS.values());
@@ -47,14 +54,16 @@ public class UserServiceImpl implements UserService {
     }
 
     @Override
-    public void create(final User user) {
+    public Response create(final User user) {
         if (user.getKey() == null) {
             user.setKey(UUID.randomUUID());
         }
         if (USERS.containsKey(user.getKey())) {
-            throw new IllegalArgumentException("User already exists: " + user.getKey());
+            throw new ClientErrorException("User already exists: " + user.getKey(), Response.Status.CONFLICT);
         }
         USERS.put(user.getKey(), user);
+
+        return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getKey().toString()).build()).build();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/syncope/blob/3e2ebb92/fit/build-tools/src/main/resources/META-INF/cxf/org.apache.cxf.Logger
----------------------------------------------------------------------
diff --git a/fit/build-tools/src/main/resources/META-INF/cxf/org.apache.cxf.Logger b/fit/build-tools/src/main/resources/META-INF/cxf/org.apache.cxf.Logger
new file mode 100644
index 0000000..6e7bd36
--- /dev/null
+++ b/fit/build-tools/src/main/resources/META-INF/cxf/org.apache.cxf.Logger
@@ -0,0 +1 @@
+org.apache.cxf.common.logging.Slf4jLogger

http://git-wip-us.apache.org/repos/asf/syncope/blob/3e2ebb92/fit/build-tools/src/main/resources/cxfContext.xml
----------------------------------------------------------------------
diff --git a/fit/build-tools/src/main/resources/cxfContext.xml b/fit/build-tools/src/main/resources/cxfContext.xml
index 9a6c959..7e53a71 100644
--- a/fit/build-tools/src/main/resources/cxfContext.xml
+++ b/fit/build-tools/src/main/resources/cxfContext.xml
@@ -38,6 +38,23 @@ under the License.
       
   <jaxws:endpoint id="soapProvisioning" address="/soap" implementor="#provisioningImpl"/>
   
+  <bean id="swagger2customizer" class="org.apache.cxf.jaxrs.swagger.Swagger2Customizer">
+    <property name="dynamicBasePath" value="true"/>
+    <property name="replaceTags" value="false"/>
+  </bean>
+  <bean id="swagger2Feature" class="org.apache.cxf.jaxrs.swagger.Swagger2Feature">
+    <property name="title" value="Apache Syncope FIT Build Tools"/>
+    <property name="version" value="${syncope.version}"/>
+    <property name="description" value="Apache Syncope ${syncope.version}"/>    
+    <property name="contact" value="dev@syncope.apache.org"/>    
+    
+    <property name="resourcePackage" value="org.apache.syncope.fit.buildtools.cxf"/>
+    <property name="scanAllResources" value="true"/>
+    <property name="activateOnlyIfJaxrsSupported" value="true"/>
+    
+    <property name="customizer" ref="swagger2customizer"/>
+  </bean>
+  
   <bean id="jsonProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider"/>
   <jaxrs:server id="restProvisioning" address="/rest"
                 basePackages="org.apache.syncope.fit.buildtools.cxf" 
@@ -45,6 +62,9 @@ under the License.
     <jaxrs:providers>
       <ref bean="jsonProvider"/>
     </jaxrs:providers>
+    <jaxrs:features>
+      <ref bean="swagger2Feature"/>
+    </jaxrs:features>
   </jaxrs:server>
 
 </beans>


[07/11] syncope git commit: Upgrading Spring Security and Groovy

Posted by il...@apache.org.
Upgrading Spring Security and Groovy


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/d5056275
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/d5056275
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/d5056275

Branch: refs/heads/master
Commit: d50562757b1f5e2c90294b763bac920025e208a7
Parents: dffa086
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Fri Mar 2 11:52:32 2018 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Mar 2 11:52:32 2018 +0100

----------------------------------------------------------------------
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/d5056275/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 522db78..a66b036 100644
--- a/pom.xml
+++ b/pom.xml
@@ -370,7 +370,7 @@ under the License.
     <jackson.version>2.9.4</jackson.version>
 
     <spring.version>5.0.4.RELEASE</spring.version>
-    <spring-security.version>5.0.2.RELEASE</spring-security.version>
+    <spring-security.version>5.0.3.RELEASE</spring-security.version>
 
     <openjpa.version>3.0.0-SNAPSHOT</openjpa.version>
     <hikaricp.version>2.7.8</hikaricp.version>
@@ -382,7 +382,7 @@ under the License.
 
     <cocoon.version>3.0.0-alpha-3</cocoon.version>
 
-    <groovy.version>2.5.0-beta-3</groovy.version>
+    <groovy.version>3.0.0-alpha-1</groovy.version>
 
     <flowable.version>6.2.1</flowable.version>
 


[03/11] syncope git commit: Upgrading groovy

Posted by il...@apache.org.
Upgrading groovy


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/988dfee0
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/988dfee0
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/988dfee0

Branch: refs/heads/2_0_X
Commit: 988dfee008e14fe642be68b5d0064e25c74cd599
Parents: 990a465
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Fri Mar 2 09:08:40 2018 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Mar 2 09:08:40 2018 +0100

----------------------------------------------------------------------
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/988dfee0/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index b6b9ad1..f95e579 100644
--- a/pom.xml
+++ b/pom.xml
@@ -382,7 +382,7 @@ under the License.
 
     <cocoon.version>3.0.0-alpha-3</cocoon.version>
 
-    <groovy.version>2.4.13</groovy.version>
+    <groovy.version>2.4.14</groovy.version>
 
     <activiti.version>5.22.0</activiti.version>
     <flowable.version>5.23.0</flowable.version>


[05/11] syncope git commit: [SYNCOPE-1279] Now providing runtime status updates from running Tasks and Reports

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java
deleted file mode 100644
index c019639..0000000
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java
+++ /dev/null
@@ -1,278 +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.core.provisioning.java.job.notification;
-
-import java.io.PrintStream;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.Properties;
-import javax.mail.Session;
-import javax.mail.internet.MimeMessage;
-import org.apache.commons.lang3.BooleanUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.lib.LogOutputStream;
-import org.apache.syncope.common.lib.PropertyUtils;
-import org.apache.syncope.common.lib.types.AuditElements;
-import org.apache.syncope.common.lib.types.TaskType;
-import org.apache.syncope.common.lib.types.TraceLevel;
-import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
-import org.apache.syncope.core.persistence.api.dao.TaskDAO;
-import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
-import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
-import org.apache.syncope.core.provisioning.api.AuditManager;
-import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
-import org.apache.syncope.core.spring.security.Encryptor;
-import org.quartz.JobExecutionException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.mail.javamail.JavaMailSender;
-import org.springframework.mail.javamail.JavaMailSenderImpl;
-import org.springframework.mail.javamail.MimeMessageHelper;
-import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
-
-@Component
-public class NotificationJobDelegate implements InitializingBean {
-
-    private static final Logger LOG = LoggerFactory.getLogger(NotificationJobDelegate.class);
-
-    @Autowired
-    private TaskDAO taskDAO;
-
-    @Autowired
-    private JavaMailSender mailSender;
-
-    @Autowired
-    private EntityFactory entityFactory;
-
-    @Autowired
-    private AuditManager auditManager;
-
-    @Autowired
-    private NotificationManager notificationManager;
-
-    @Override
-    public void afterPropertiesSet() throws Exception {
-        if (mailSender instanceof JavaMailSenderImpl) {
-            JavaMailSenderImpl javaMailSender = (JavaMailSenderImpl) mailSender;
-
-            Properties javaMailProperties = javaMailSender.getJavaMailProperties();
-
-            Properties props = PropertyUtils.read(Encryptor.class, "mail.properties", "conf.directory").getLeft();
-            for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) {
-                String prop = (String) e.nextElement();
-                if (prop.startsWith("mail.smtp.")) {
-                    javaMailProperties.setProperty(prop, props.getProperty(prop));
-                }
-            }
-
-            if (StringUtils.isNotBlank(javaMailSender.getUsername())) {
-                javaMailProperties.setProperty("mail.smtp.auth", "true");
-            }
-
-            javaMailSender.setJavaMailProperties(javaMailProperties);
-
-            String mailDebug = props.getProperty("mail.debug", "false");
-            if (BooleanUtils.toBoolean(mailDebug)) {
-                Session session = javaMailSender.getSession();
-                session.setDebug(true);
-                session.setDebugOut(new PrintStream(new LogOutputStream(LOG)));
-            }
-        }
-    }
-
-    @Transactional
-    public TaskExec executeSingle(final NotificationTask task) {
-        TaskExec execution = entityFactory.newEntity(TaskExec.class);
-        execution.setTask(task);
-        execution.setStart(new Date());
-
-        boolean retryPossible = true;
-
-        if (StringUtils.isBlank(task.getSubject()) || task.getRecipients().isEmpty()
-                || StringUtils.isBlank(task.getHtmlBody()) || StringUtils.isBlank(task.getTextBody())) {
-
-            String message = "Could not fetch all required information for sending e-mails:\n"
-                    + task.getRecipients() + "\n"
-                    + task.getSender() + "\n"
-                    + task.getSubject() + "\n"
-                    + task.getHtmlBody() + "\n"
-                    + task.getTextBody();
-            LOG.error(message);
-
-            execution.setStatus(NotificationJob.Status.NOT_SENT.name());
-            retryPossible = false;
-
-            if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
-                execution.setMessage(message);
-            }
-        } else {
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("About to send e-mails:\n"
-                        + task.getRecipients() + "\n"
-                        + task.getSender() + "\n"
-                        + task.getSubject() + "\n"
-                        + task.getHtmlBody() + "\n"
-                        + task.getTextBody() + "\n");
-            }
-
-            for (String to : task.getRecipients()) {
-                try {
-                    MimeMessage message = mailSender.createMimeMessage();
-                    MimeMessageHelper helper = new MimeMessageHelper(message, true);
-                    helper.setTo(to);
-                    helper.setFrom(task.getSender());
-                    helper.setSubject(task.getSubject());
-                    helper.setText(task.getTextBody(), task.getHtmlBody());
-
-                    mailSender.send(message);
-
-                    execution.setStatus(NotificationJob.Status.SENT.name());
-
-                    StringBuilder report = new StringBuilder();
-                    switch (task.getTraceLevel()) {
-                        case ALL:
-                            report.append("FROM: ").append(task.getSender()).append('\n').
-                                    append("TO: ").append(to).append('\n').
-                                    append("SUBJECT: ").append(task.getSubject()).append('\n').append('\n').
-                                    append(task.getTextBody()).append('\n').append('\n').
-                                    append(task.getHtmlBody()).append('\n');
-                            break;
-
-                        case SUMMARY:
-                            report.append("E-mail sent to ").append(to).append('\n');
-                            break;
-
-                        case FAILURES:
-                        case NONE:
-                        default:
-                    }
-                    if (report.length() > 0) {
-                        execution.setMessage(report.toString());
-                    }
-
-                    notificationManager.createTasks(
-                            AuditElements.EventCategoryType.TASK,
-                            "notification",
-                            null,
-                            "send",
-                            AuditElements.Result.SUCCESS,
-                            null,
-                            null,
-                            task,
-                            "Successfully sent notification to " + to);
-                } catch (Exception e) {
-                    LOG.error("Could not send e-mail", e);
-
-                    execution.setStatus(NotificationJob.Status.NOT_SENT.name());
-                    if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
-                        execution.setMessage(ExceptionUtils2.getFullStackTrace(e));
-                    }
-
-                    notificationManager.createTasks(
-                            AuditElements.EventCategoryType.TASK,
-                            "notification",
-                            null,
-                            "send",
-                            AuditElements.Result.FAILURE,
-                            null,
-                            null,
-                            task,
-                            "Could not send notification to " + to, e);
-                }
-
-                execution.setEnd(new Date());
-            }
-        }
-
-        if (hasToBeRegistered(execution)) {
-            execution = notificationManager.storeExec(execution);
-            if (retryPossible
-                    && (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT)) {
-
-                handleRetries(execution);
-            }
-        } else {
-            notificationManager.setTaskExecuted(execution.getTask().getKey(), true);
-        }
-
-        return execution;
-    }
-
-    @Transactional
-    public void execute() throws JobExecutionException {
-        for (NotificationTask task : taskDAO.<NotificationTask>findToExec(TaskType.NOTIFICATION)) {
-            LOG.debug("Found notification task {} to be executed: starting...", task);
-            executeSingle(task);
-            LOG.debug("Notification task {} executed", task);
-        }
-    }
-
-    private boolean hasToBeRegistered(final TaskExec execution) {
-        NotificationTask task = (NotificationTask) execution.getTask();
-
-        // True if either failed and failures have to be registered, or if ALL
-        // has to be registered.
-        return (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT
-                && task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
-                || task.getTraceLevel() == TraceLevel.ALL;
-    }
-
-    private void handleRetries(final TaskExec execution) {
-        if (notificationManager.getMaxRetries() <= 0) {
-            return;
-        }
-
-        long failedExecutionsCount = notificationManager.countExecutionsWithStatus(
-                execution.getTask().getKey(), NotificationJob.Status.NOT_SENT.name());
-
-        if (failedExecutionsCount <= notificationManager.getMaxRetries()) {
-            LOG.debug("Execution of notification task {} will be retried [{}/{}]",
-                    execution.getTask(), failedExecutionsCount, notificationManager.getMaxRetries());
-            notificationManager.setTaskExecuted(execution.getTask().getKey(), false);
-
-            auditManager.audit(
-                    AuditElements.EventCategoryType.TASK,
-                    "notification",
-                    null,
-                    "retry",
-                    AuditElements.Result.SUCCESS,
-                    null,
-                    null,
-                    execution,
-                    "Notification task " + execution.getTask().getKey() + " will be retried");
-        } else {
-            LOG.error("Maximum number of retries reached for task {} - giving up", execution.getTask());
-
-            auditManager.audit(
-                    AuditElements.EventCategoryType.TASK,
-                    "notification",
-                    null,
-                    "retry",
-                    AuditElements.Result.FAILURE,
-                    null,
-                    null,
-                    execution,
-                    "Giving up retries on notification task " + execution.getTask().getKey());
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java
index 2c85b20..81a108c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.java.job.report;
 
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.core.persistence.api.dao.Reportlet;
 import org.apache.syncope.common.lib.report.ReportletConf;
 import org.slf4j.Logger;
@@ -31,11 +32,17 @@ public abstract class AbstractReportlet implements Reportlet {
 
     protected static final Logger LOG = LoggerFactory.getLogger(AbstractReportlet.class);
 
-    protected abstract void doExtract(ReportletConf conf, ContentHandler handler) throws SAXException;
+    protected abstract void doExtract(ReportletConf conf, ContentHandler handler, AtomicReference<String> status)
+            throws SAXException;
 
     @Override
     @Transactional(readOnly = true)
-    public void extract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+    public void extract(
+            final ReportletConf conf,
+            final ContentHandler handler,
+            final AtomicReference<String> status)
+            throws SAXException {
+
         if (conf == null) {
             throw new ReportException(new IllegalArgumentException("No configuration provided"));
         }
@@ -45,7 +52,7 @@ public abstract class AbstractReportlet implements Reportlet {
         atts.addAttribute("", "", ReportXMLConst.ATTR_CLASS, ReportXMLConst.XSD_STRING, getClass().getName());
         handler.startElement("", "", ReportXMLConst.ELEMENT_REPORTLET, atts);
 
-        doExtract(conf, handler);
+        doExtract(conf, handler, status);
 
         handler.endElement("", "", ReportXMLConst.ELEMENT_REPORTLET);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
index d6f0ec5..6ba9a53 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.java.job.report;
 
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 import javax.sql.DataSource;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -47,7 +48,9 @@ public class AuditReportlet extends AbstractReportlet {
 
     private DataSource datasource;
 
-    private void doExtractConf(final ContentHandler handler) throws SAXException {
+    private void doExtractConf(final ContentHandler handler, final AtomicReference<String> status) throws SAXException {
+        status.set("Fetching " + conf.getSize() + " rows from the SYNCOPEAUDIT table");
+
         JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource);
         jdbcTemplate.setMaxRows(conf.getSize());
         List<Map<String, Object>> rows = jdbcTemplate.
@@ -120,10 +123,17 @@ public class AuditReportlet extends AbstractReportlet {
             handler.endElement("", "", "event");
         }
         handler.endElement("", "", "events");
+
+        status.set("Fetched " + conf.getSize() + " rows from the SYNCOPEAUDIT table");
     }
 
     @Override
-    protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+    protected void doExtract(
+            final ReportletConf conf,
+            final ContentHandler handler,
+            final AtomicReference<String> status)
+            throws SAXException {
+
         if (conf instanceof AuditReportletConf) {
             this.conf = AuditReportletConf.class.cast(conf);
         } else {
@@ -135,7 +145,7 @@ public class AuditReportlet extends AbstractReportlet {
             throw new ReportException(new IllegalArgumentException("Could not get to DataSource"));
         }
 
-        doExtractConf(handler);
+        doExtractConf(handler, status);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java
new file mode 100644
index 0000000..a9aeab0
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java
@@ -0,0 +1,216 @@
+/*
+ * 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.core.provisioning.java.job.report;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.zip.Deflater;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.common.lib.types.ReportExecStatus;
+import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
+import org.apache.syncope.core.persistence.api.dao.ReportDAO;
+import org.apache.syncope.core.persistence.api.dao.ReportExecDAO;
+import org.apache.syncope.core.persistence.api.dao.Reportlet;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.Report;
+import org.apache.syncope.core.persistence.api.entity.ReportExec;
+import org.apache.syncope.core.provisioning.api.job.report.ReportJobDelegate;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.xml.sax.helpers.AttributesImpl;
+
+@Component
+public class DefaultReportJobDelegate implements ReportJobDelegate {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ReportJobDelegate.class);
+
+    /**
+     * Report DAO.
+     */
+    @Autowired
+    private ReportDAO reportDAO;
+
+    /**
+     * Report execution DAO.
+     */
+    @Autowired
+    private ReportExecDAO reportExecDAO;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Autowired
+    private ImplementationLookup implementationLookup;
+
+    private final AtomicReference<String> status = new AtomicReference<>();
+
+    @Override
+    public String currentStatus() {
+        return status.get();
+    }
+
+    @Transactional
+    @Override
+    public void execute(final String reportKey) throws JobExecutionException {
+        Report report = reportDAO.find(reportKey);
+        if (report == null) {
+            throw new JobExecutionException("Report " + reportKey + " not found");
+        }
+
+        if (!report.isActive()) {
+            LOG.info("Report {} not active, aborting...", reportKey);
+            return;
+        }
+
+        // 1. create execution
+        ReportExec execution = entityFactory.newEntity(ReportExec.class);
+        execution.setStatus(ReportExecStatus.STARTED);
+        execution.setStart(new Date());
+        execution.setReport(report);
+        execution = reportExecDAO.save(execution);
+
+        report.add(execution);
+        report = reportDAO.save(report);
+
+        // 2. define a SAX handler for generating result as XML
+        TransformerHandler handler;
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ZipOutputStream zos = new ZipOutputStream(baos);
+        zos.setLevel(Deflater.BEST_COMPRESSION);
+        try {
+            SAXTransformerFactory tFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
+            tFactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
+            handler = tFactory.newTransformerHandler();
+            Transformer serializer = handler.getTransformer();
+            serializer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
+            serializer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+            // a single ZipEntry in the ZipOutputStream
+            zos.putNextEntry(new ZipEntry(report.getName()));
+
+            // streaming SAX handler in a compressed byte array stream
+            handler.setResult(new StreamResult(zos));
+        } catch (Exception e) {
+            throw new JobExecutionException("While configuring for SAX generation", e, true);
+        }
+
+        execution.setStatus(ReportExecStatus.RUNNING);
+        execution = reportExecDAO.save(execution);
+
+        status.set("Starting");
+
+        // 3. actual report execution
+        StringBuilder reportExecutionMessage = new StringBuilder();
+        try {
+            // report header
+            handler.startDocument();
+            AttributesImpl atts = new AttributesImpl();
+            atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, report.getName());
+            handler.startElement("", "", ReportXMLConst.ELEMENT_REPORT, atts);
+
+            status.set("Generating report header");
+
+            // iterate over reportlet instances defined for this report
+            for (ReportletConf reportletConf : report.getReportletConfs()) {
+                Class<? extends Reportlet> reportletClass =
+                        implementationLookup.getReportletClass(reportletConf.getClass());
+                if (reportletClass == null) {
+                    LOG.warn("Could not find matching reportlet for {}", reportletConf.getClass());
+                } else {
+                    // fetch (or create) reportlet
+                    Reportlet reportlet;
+                    if (ApplicationContextProvider.getBeanFactory().containsSingleton(reportletClass.getName())) {
+                        reportlet = (Reportlet) ApplicationContextProvider.getBeanFactory().
+                                getSingleton(reportletClass.getName());
+                    } else {
+                        reportlet = (Reportlet) ApplicationContextProvider.getBeanFactory().
+                                createBean(reportletClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+                        ApplicationContextProvider.getBeanFactory().
+                                registerSingleton(reportletClass.getName(), reportlet);
+                    }
+
+                    // invoke reportlet
+                    try {
+                        status.set("Invoking reportlet " + reportletClass.getName());
+                        reportlet.extract(reportletConf, handler, status);
+                    } catch (Throwable t) {
+                        LOG.error("While executing reportlet {} for report {}", reportlet, reportKey, t);
+
+                        execution.setStatus(ReportExecStatus.FAILURE);
+
+                        Throwable effective = t instanceof ReportException
+                                ? t.getCause()
+                                : t;
+                        reportExecutionMessage.
+                                append(ExceptionUtils2.getFullStackTrace(effective)).
+                                append("\n==================\n");
+                    }
+                }
+            }
+
+            // report footer
+            status.set("Generating report footer");
+
+            handler.endElement("", "", ReportXMLConst.ELEMENT_REPORT);
+            handler.endDocument();
+
+            if (!ReportExecStatus.FAILURE.name().equals(execution.getStatus())) {
+                execution.setStatus(ReportExecStatus.SUCCESS);
+            }
+        } catch (Exception e) {
+            execution.setStatus(ReportExecStatus.FAILURE);
+            reportExecutionMessage.append(ExceptionUtils2.getFullStackTrace(e));
+
+            throw new JobExecutionException(e, true);
+        } finally {
+            status.set("Completed");
+
+            try {
+                zos.closeEntry();
+                zos.close();
+                baos.close();
+            } catch (IOException e) {
+                LOG.error("While closing StreamResult's backend", e);
+            }
+
+            execution.setExecResult(baos.toByteArray());
+            execution.setMessage(reportExecutionMessage.toString());
+            execution.setEnd(new Date());
+            reportExecDAO.save(execution);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
index 44a4dfa..b34e271 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.EntityTOUtils;
 import org.apache.syncope.common.lib.SyncopeConstants;
@@ -288,7 +289,12 @@ public class GroupReportlet extends AbstractReportlet {
     }
 
     @Override
-    protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+    protected void doExtract(
+            final ReportletConf conf,
+            final ContentHandler handler,
+            final AtomicReference<String> status)
+            throws SAXException {
+
         if (conf instanceof GroupReportletConf) {
             this.conf = GroupReportletConf.class.cast(conf);
         } else {
@@ -297,7 +303,14 @@ public class GroupReportlet extends AbstractReportlet {
 
         doExtractConf(handler);
 
-        for (int page = 1; page <= (count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+        int total = count();
+        int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+        status.set("Processing " + total + " groups in " + pages + " pages");
+
+        for (int page = 1; page <= pages; page++) {
+            status.set("Processing " + total + " groups: page " + page + " of " + pages);
+
             List<Group> groups;
             if (StringUtils.isBlank(this.conf.getMatchingCond())) {
                 groups = groupDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE);
@@ -312,6 +325,8 @@ public class GroupReportlet extends AbstractReportlet {
             }
 
             doExtract(handler, groups);
+
+            status.set("Processed " + total + " groups: page " + page + " of " + pages);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
index 0328e62..aac1888 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.collections4.Closure;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.IterableUtils;
@@ -50,7 +51,6 @@ import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
-import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
@@ -373,25 +373,13 @@ public class ReconciliationReportlet extends AbstractReportlet {
         }
     }
 
-    private void doExtract(
-            final ContentHandler handler, final int count, final SearchCond cond, final AnyTypeKind anyTypeKind)
+    @Override
+    protected void doExtract(
+            final ReportletConf conf,
+            final ContentHandler handler,
+            final AtomicReference<String> status)
             throws SAXException {
 
-        for (int page = 1; page <= (count / PAGE_SIZE) + 1; page++) {
-            List<AnyObject> anys = searchDAO.search(
-                    SyncopeConstants.FULL_ADMIN_REALMS,
-                    cond,
-                    page,
-                    PAGE_SIZE,
-                    Collections.<OrderByClause>emptyList(),
-                    anyTypeKind);
-
-            doExtract(handler, anys);
-        }
-    }
-
-    @Override
-    protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
         if (conf instanceof ReconciliationReportletConf) {
             this.conf = ReconciliationReportletConf.class.cast(conf);
         } else {
@@ -401,39 +389,81 @@ public class ReconciliationReportlet extends AbstractReportlet {
         AttributesImpl atts = new AttributesImpl();
 
         if (StringUtils.isBlank(this.conf.getUserMatchingCond())) {
-            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(userDAO.count()));
+            int total = userDAO.count();
+            int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+            status.set("Processing " + total + " users in " + pages + " pages");
+
+            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(total));
             handler.startElement("", "", getAnyElementName(AnyTypeKind.USER) + "s", atts);
 
-            for (int page = 1; page <= (userDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+            for (int page = 1; page <= pages; page++) {
+                status.set("Processing " + total + " users: page " + page + " of " + pages);
+
                 doExtract(handler, userDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE));
             }
         } else {
             SearchCond cond = SearchCondConverter.convert(this.conf.getUserMatchingCond());
 
-            int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.USER);
-            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(count));
+            int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.USER);
+            int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+            status.set("Processing " + total + " users in " + pages + " pages");
+
+            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(total));
             handler.startElement("", "", getAnyElementName(AnyTypeKind.USER) + "s", atts);
 
-            doExtract(handler, count, cond, AnyTypeKind.USER);
+            for (int page = 1; page <= pages; page++) {
+                status.set("Processing " + total + " users: page " + page + " of " + pages);
+
+                doExtract(handler, searchDAO.search(
+                        SyncopeConstants.FULL_ADMIN_REALMS,
+                        cond,
+                        page,
+                        PAGE_SIZE,
+                        Collections.<OrderByClause>emptyList(),
+                        AnyTypeKind.USER));
+            }
         }
         handler.endElement("", "", getAnyElementName(AnyTypeKind.USER) + "s");
 
         atts.clear();
         if (StringUtils.isBlank(this.conf.getGroupMatchingCond())) {
-            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(groupDAO.count()));
+            int total = groupDAO.count();
+            int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+            status.set("Processing " + total + " groups in " + pages + " pages");
+
+            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(total));
             handler.startElement("", "", getAnyElementName(AnyTypeKind.GROUP) + "s", atts);
 
-            for (int page = 1; page <= (groupDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+            for (int page = 1; page <= pages; page++) {
+                status.set("Processing " + total + " groups: page " + page + " of " + pages);
+
                 doExtract(handler, groupDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE));
             }
         } else {
             SearchCond cond = SearchCondConverter.convert(this.conf.getUserMatchingCond());
 
-            int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.GROUP);
-            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(count));
+            int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.GROUP);
+            int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+            status.set("Processing " + total + " groups in " + pages + " pages");
+
+            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(total));
             handler.startElement("", "", getAnyElementName(AnyTypeKind.GROUP) + "s", atts);
 
-            doExtract(handler, count, cond, AnyTypeKind.GROUP);
+            for (int page = 1; page <= pages; page++) {
+                status.set("Processing " + total + " groups: page " + page + " of " + pages);
+
+                doExtract(handler, searchDAO.search(
+                        SyncopeConstants.FULL_ADMIN_REALMS,
+                        cond,
+                        page,
+                        PAGE_SIZE,
+                        Collections.<OrderByClause>emptyList(),
+                        AnyTypeKind.GROUP));
+            }
         }
         handler.endElement("", "", getAnyElementName(AnyTypeKind.GROUP) + "s");
 
@@ -447,14 +477,28 @@ public class ReconciliationReportlet extends AbstractReportlet {
                                 SearchCond.getLeafCond(anyTypeCond),
                                 SearchCondConverter.convert(this.conf.getAnyObjectMatchingCond()));
 
-                int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.ANY_OBJECT);
+                int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.ANY_OBJECT);
+                int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+                status.set("Processing " + total + " any objects " + anyType.getKey() + " in " + pages + " pages");
 
                 atts.clear();
                 atts.addAttribute("", "", "type", ReportXMLConst.XSD_STRING, anyType.getKey());
-                atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(count));
+                atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(total));
                 handler.startElement("", "", getAnyElementName(AnyTypeKind.ANY_OBJECT) + "s", atts);
 
-                doExtract(handler, count, cond, AnyTypeKind.ANY_OBJECT);
+                for (int page = 1; page <= pages; page++) {
+                    status.set("Processing " + total + " any objects " + anyType.getKey()
+                            + ": page " + page + " of " + pages);
+
+                    doExtract(handler, searchDAO.search(
+                            SyncopeConstants.FULL_ADMIN_REALMS,
+                            cond,
+                            page,
+                            PAGE_SIZE,
+                            Collections.<OrderByClause>emptyList(),
+                            AnyTypeKind.ANY_OBJECT));
+                }
 
                 handler.endElement("", "", getAnyElementName(AnyTypeKind.ANY_OBJECT) + "s");
             }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java
index 78183d5..5ef6785 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java
@@ -18,12 +18,14 @@
  */
 package org.apache.syncope.core.provisioning.java.job.report;
 
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.provisioning.java.job.AbstractInterruptableJob;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.apache.syncope.core.provisioning.api.job.JobManager;
+import org.apache.syncope.core.provisioning.api.job.report.ReportJobDelegate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,6 +54,11 @@ public class ReportJob extends AbstractInterruptableJob {
     }
 
     @Override
+    public JobDelegate getDelegate() {
+        return delegate;
+    }
+
+    @Override
     public void execute(final JobExecutionContext context) throws JobExecutionException {
         super.execute(context);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java
deleted file mode 100644
index 40d9250..0000000
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java
+++ /dev/null
@@ -1,197 +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.core.provisioning.java.job.report;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Date;
-import java.util.zip.Deflater;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.sax.SAXTransformerFactory;
-import javax.xml.transform.sax.TransformerHandler;
-import javax.xml.transform.stream.StreamResult;
-import org.apache.syncope.common.lib.report.ReportletConf;
-import org.apache.syncope.common.lib.types.ReportExecStatus;
-import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
-import org.apache.syncope.core.spring.ApplicationContextProvider;
-import org.apache.syncope.core.persistence.api.ImplementationLookup;
-import org.apache.syncope.core.persistence.api.dao.ReportDAO;
-import org.apache.syncope.core.persistence.api.dao.ReportExecDAO;
-import org.apache.syncope.core.persistence.api.dao.Reportlet;
-import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.api.entity.Report;
-import org.apache.syncope.core.persistence.api.entity.ReportExec;
-import org.quartz.JobExecutionException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.support.AbstractBeanDefinition;
-import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
-import org.xml.sax.helpers.AttributesImpl;
-
-@Component
-public class ReportJobDelegate {
-
-    private static final Logger LOG = LoggerFactory.getLogger(ReportJobDelegate.class);
-
-    /**
-     * Report DAO.
-     */
-    @Autowired
-    private ReportDAO reportDAO;
-
-    /**
-     * Report execution DAO.
-     */
-    @Autowired
-    private ReportExecDAO reportExecDAO;
-
-    @Autowired
-    private EntityFactory entityFactory;
-
-    @Autowired
-    private ImplementationLookup implementationLookup;
-
-    @Transactional
-    public void execute(final String reportKey) throws JobExecutionException {
-        Report report = reportDAO.find(reportKey);
-        if (report == null) {
-            throw new JobExecutionException("Report " + reportKey + " not found");
-        }
-
-        if (!report.isActive()) {
-            LOG.info("Report {} not active, aborting...", reportKey);
-            return;
-        }
-
-        // 1. create execution
-        ReportExec execution = entityFactory.newEntity(ReportExec.class);
-        execution.setStatus(ReportExecStatus.STARTED);
-        execution.setStart(new Date());
-        execution.setReport(report);
-        execution = reportExecDAO.save(execution);
-
-        report.add(execution);
-        report = reportDAO.save(report);
-
-        // 2. define a SAX handler for generating result as XML
-        TransformerHandler handler;
-
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        ZipOutputStream zos = new ZipOutputStream(baos);
-        zos.setLevel(Deflater.BEST_COMPRESSION);
-        try {
-            SAXTransformerFactory tFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
-            tFactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
-            handler = tFactory.newTransformerHandler();
-            Transformer serializer = handler.getTransformer();
-            serializer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
-            serializer.setOutputProperty(OutputKeys.INDENT, "yes");
-
-            // a single ZipEntry in the ZipOutputStream
-            zos.putNextEntry(new ZipEntry(report.getName()));
-
-            // streaming SAX handler in a compressed byte array stream
-            handler.setResult(new StreamResult(zos));
-        } catch (Exception e) {
-            throw new JobExecutionException("While configuring for SAX generation", e, true);
-        }
-
-        execution.setStatus(ReportExecStatus.RUNNING);
-        execution = reportExecDAO.save(execution);
-
-        // 3. actual report execution
-        StringBuilder reportExecutionMessage = new StringBuilder();
-        try {
-            // report header
-            handler.startDocument();
-            AttributesImpl atts = new AttributesImpl();
-            atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, report.getName());
-            handler.startElement("", "", ReportXMLConst.ELEMENT_REPORT, atts);
-
-            // iterate over reportlet instances defined for this report
-            for (ReportletConf reportletConf : report.getReportletConfs()) {
-                Class<? extends Reportlet> reportletClass =
-                        implementationLookup.getReportletClass(reportletConf.getClass());
-                if (reportletClass == null) {
-                    LOG.warn("Could not find matching reportlet for {}", reportletConf.getClass());
-                } else {
-                    // fetch (or create) reportlet
-                    Reportlet reportlet;
-                    if (ApplicationContextProvider.getBeanFactory().containsSingleton(reportletClass.getName())) {
-                        reportlet = (Reportlet) ApplicationContextProvider.getBeanFactory().
-                                getSingleton(reportletClass.getName());
-                    } else {
-                        reportlet = (Reportlet) ApplicationContextProvider.getBeanFactory().
-                                createBean(reportletClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
-                        ApplicationContextProvider.getBeanFactory().
-                                registerSingleton(reportletClass.getName(), reportlet);
-                    }
-
-                    // invoke reportlet
-                    try {
-                        reportlet.extract(reportletConf, handler);
-                    } catch (Throwable t) {
-                        LOG.error("While executing reportlet {} for report {}", reportlet, reportKey, t);
-
-                        execution.setStatus(ReportExecStatus.FAILURE);
-
-                        Throwable effective = t instanceof ReportException
-                                ? t.getCause()
-                                : t;
-                        reportExecutionMessage.
-                                append(ExceptionUtils2.getFullStackTrace(effective)).
-                                append("\n==================\n");
-                    }
-                }
-            }
-
-            // report footer
-            handler.endElement("", "", ReportXMLConst.ELEMENT_REPORT);
-            handler.endDocument();
-
-            if (!ReportExecStatus.FAILURE.name().equals(execution.getStatus())) {
-                execution.setStatus(ReportExecStatus.SUCCESS);
-            }
-        } catch (Exception e) {
-            execution.setStatus(ReportExecStatus.FAILURE);
-            reportExecutionMessage.append(ExceptionUtils2.getFullStackTrace(e));
-
-            throw new JobExecutionException(e, true);
-        } finally {
-            try {
-                zos.closeEntry();
-                zos.close();
-                baos.close();
-            } catch (IOException e) {
-                LOG.error("While closing StreamResult's backend", e);
-            }
-
-            execution.setExecResult(baos.toByteArray());
-            execution.setMessage(reportExecutionMessage.toString());
-            execution.setEnd(new Date());
-            reportExecDAO.save(execution);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java
index 1156d25..49e5e2a 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.java.job.report;
 
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.core.persistence.api.dao.ReportletConfClass;
 import org.apache.syncope.common.lib.report.ReportletConf;
 import org.apache.syncope.common.lib.report.StaticReportletConf;
@@ -70,7 +71,12 @@ public class StaticReportlet extends AbstractReportlet {
     }
 
     @Override
-    protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+    protected void doExtract(
+            final ReportletConf conf,
+            final ContentHandler handler,
+            final AtomicReference<String> status)
+            throws SAXException {
+
         if (conf instanceof StaticReportletConf) {
             this.conf = StaticReportletConf.class.cast(conf);
         } else {

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
index 5ab0e78..0c13b27 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.EntityTOUtils;
 import org.apache.syncope.common.lib.SyncopeConstants;
@@ -355,7 +356,12 @@ public class UserReportlet extends AbstractReportlet {
     }
 
     @Override
-    protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+    protected void doExtract(
+            final ReportletConf conf,
+            final ContentHandler handler,
+            final AtomicReference<String> status)
+            throws SAXException {
+
         if (conf instanceof UserReportletConf) {
             this.conf = UserReportletConf.class.cast(conf);
         } else {
@@ -364,7 +370,14 @@ public class UserReportlet extends AbstractReportlet {
 
         doExtractConf(handler);
 
-        for (int page = 1; page <= (count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+        int total = count();
+        int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+        status.set("Processing " + total + " users in " + pages + " pages");
+
+        for (int page = 1; page <= pages; page++) {
+            status.set("Processing " + total + " users: page " + page + " of " + pages);
+
             List<User> users;
             if (StringUtils.isBlank(this.conf.getMatchingCond())) {
                 users = userDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE);

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index 66e864f..3357335 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -28,6 +28,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.collections4.IteratorUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.to.ExecTO;
@@ -209,7 +210,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             final PropagationTask task,
             final ConnectorObject beforeObj,
             final Connector connector,
-            final Boolean[] propagationAttempted) {
+            final AtomicReference<Boolean> propagationAttempted) {
 
         // set of attributes to be propagated
         Set<Attribute> attributes = new HashSet<>(task.getAttributes());
@@ -299,7 +300,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             final PropagationTask task,
             final ConnectorObject beforeObj,
             final Connector connector,
-            final Boolean[] propagationAttempted) {
+            final AtomicReference<Boolean> propagationAttempted) {
 
         Uid result;
         if (beforeObj == null) {
@@ -392,7 +393,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         String failureReason = null;
 
         // Flag to state whether any propagation has been attempted
-        Boolean[] propagationAttempted = new Boolean[] { false };
+        AtomicReference<Boolean> propagationAttempted = new AtomicReference<>(false);
 
         ConnectorObject beforeObj = null;
         ConnectorObject afterObj = null;
@@ -431,7 +432,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                 default:
             }
 
-            execution.setStatus(propagationAttempted[0]
+            execution.setStatus(propagationAttempted.get()
                     ? PropagationTaskExecStatus.SUCCESS.name()
                     : PropagationTaskExecStatus.NOT_ATTEMPTED.name());
 
@@ -463,7 +464,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                 LOG.error("While executing KO action on {}", execution, wft);
             }
 
-            propagationAttempted[0] = true;
+            propagationAttempted.set(true);
 
             for (PropagationActions action : actions) {
                 action.onError(task, execution, e);

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
index d1853c2..ef32fc9 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
@@ -130,6 +130,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
             }
 
             doHandle(delta, provision);
+            executor.reportHandled(delta.getObjectClass(), delta.getObject().getName());
 
             LOG.debug("Successfully handled {}", delta);
 
@@ -159,6 +160,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
             LOG.warn("Ignoring during pull", e);
 
             executor.setLatestSyncToken(delta.getObjectClass(), delta.getToken());
+            executor.reportHandled(delta.getObjectClass(), delta.getObject().getName());
 
             return true;
         } catch (JobExecutionException e) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
index c274dc6..6ff95fc 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
@@ -89,6 +89,7 @@ public class DefaultRealmPullResultHandler
             }
 
             doHandle(delta, orgUnit);
+            executor.reportHandled(delta.getObjectClass(), delta.getObject().getName());
 
             LOG.debug("Successfully handled {}", delta);
 
@@ -117,6 +118,7 @@ public class DefaultRealmPullResultHandler
             LOG.warn("Ignoring during pull", e);
 
             executor.setLatestSyncToken(delta.getObjectClass(), delta.getToken());
+            executor.reportHandled(delta.getObjectClass(), delta.getObject().getName());
 
             return true;
         } catch (JobExecutionException e) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
index b8c1fa2..dfb526e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import java.util.Set;
 import org.apache.commons.collections4.IteratorUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.MutablePair;
 import org.apache.syncope.common.lib.policy.PullPolicySpec;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -54,6 +55,7 @@ import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullExecutor;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.UserPullResultHandler;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+import org.identityconnectors.framework.common.objects.Name;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.identityconnectors.framework.common.objects.OperationOptions;
 import org.identityconnectors.framework.common.objects.SyncToken;
@@ -74,6 +76,8 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
 
     protected final Map<ObjectClass, SyncToken> latestSyncTokens = new HashMap<>();
 
+    protected final Map<ObjectClass, MutablePair<Integer, String>> handled = new HashMap<>();
+
     protected ProvisioningProfile<PullTask, PullActions> profile;
 
     protected RealmPullResultHandler rhandler;
@@ -89,6 +93,34 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
         latestSyncTokens.put(objectClass, latestSyncToken);
     }
 
+    @Override
+    public void reportHandled(final ObjectClass objectClass, final Name name) {
+        MutablePair<Integer, String> pair = handled.get(objectClass);
+        if (pair == null) {
+            pair = MutablePair.of(0, null);
+            handled.put(objectClass, pair);
+        }
+        pair.setLeft(pair.getLeft() + 1);
+        pair.setRight(name.getNameValue());
+    }
+
+    @Override
+    public String currentStatus() {
+        synchronized (status) {
+            if (!handled.isEmpty()) {
+                StringBuilder builder = new StringBuilder("Processed:\n");
+                for (Map.Entry<ObjectClass, MutablePair<Integer, String>> entry : handled.entrySet()) {
+                    builder.append(' ').append(entry.getValue().getLeft()).append('\t').
+                            append(entry.getKey().getObjectClassValue()).
+                            append("\t/ latest: ").append(entry.getValue().getRight()).
+                            append('\n');
+                }
+                status.set(builder.toString());
+            }
+        }
+        return status.get();
+    }
+
     protected void setGroupOwners(final GroupPullResultHandler ghandler) {
         for (Map.Entry<String, String> entry : ghandler.getGroupOwnerMap().entrySet()) {
             Group group = groupDAO.find(entry.getKey());
@@ -195,8 +227,12 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
             }
         }
 
+        status.set("Initialization completed");
+
         // First realms...
         if (pullTask.getResource().getOrgUnit() != null) {
+            status.set("Pulling " + pullTask.getResource().getOrgUnit().getObjectClass().getObjectClassValue());
+
             OrgUnit orgUnit = pullTask.getResource().getOrgUnit();
             OperationOptions options = MappingUtils.buildOperationOptions(
                     MappingUtils.getPullItems(orgUnit.getItems()).iterator());
@@ -252,6 +288,8 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
 
         for (Provision provision : pullTask.getResource().getProvisions()) {
             if (provision.getMapping() != null) {
+                status.set("Pulling " + provision.getObjectClass().getObjectClassValue());
+
                 SyncopePullResultHandler handler;
                 switch (provision.getAnyType().getKind()) {
                     case USER:
@@ -331,6 +369,8 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
             }
         }
 
+        status.set("Pull done");
+
         String result = createReport(profile.getResults(), pullTask.getResource(), dryRun);
         LOG.debug("Pull result: {}", result);
         return result;

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
index 5c8e788..abbb765 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
@@ -20,8 +20,12 @@ package org.apache.syncope.core.provisioning.java.pushpull;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.MutablePair;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
@@ -35,9 +39,12 @@ import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
+import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
+import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.pushpull.AnyObjectPushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.GroupPushResultHandler;
@@ -78,6 +85,8 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
 
     protected ProvisioningProfile<PushTask, PushActions> profile;
 
+    protected final Map<String, MutablePair<Integer, String>> handled = new HashMap<>();
+
     protected RealmPushResultHandler rhandler;
 
     protected AnyObjectPushResultHandler ahandler;
@@ -86,6 +95,33 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
 
     protected GroupPushResultHandler ghandler;
 
+    protected void reportHandled(final String anyType, final String key) {
+        MutablePair<Integer, String> pair = handled.get(anyType);
+        if (pair == null) {
+            pair = MutablePair.of(0, null);
+            handled.put(anyType, pair);
+        }
+        pair.setLeft(pair.getLeft() + 1);
+        pair.setRight(key);
+    }
+
+    @Override
+    public String currentStatus() {
+        synchronized (status) {
+            if (!handled.isEmpty()) {
+                StringBuilder builder = new StringBuilder("Processed:\n");
+                for (Map.Entry<String, MutablePair<Integer, String>> entry : handled.entrySet()) {
+                    builder.append(' ').append(entry.getValue().getLeft()).append('\t').
+                            append(entry.getKey()).
+                            append("\t/ latest: ").append(entry.getValue().getRight()).
+                            append('\n');
+                }
+                status.set(builder.toString());
+            }
+        }
+        return status.get();
+    }
+
     protected AnyDAO<?> getAnyDAO(final AnyTypeKind anyTypeKind) {
         AnyDAO<?> result;
         switch (anyTypeKind) {
@@ -114,6 +150,13 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         for (Any<?> any : anys) {
             try {
                 handler.handle(any.getKey());
+                reportHandled(
+                        any.getType().getKey(),
+                        (any instanceof User
+                                ? ((User) any).getUsername()
+                                : any instanceof Group
+                                        ? ((Group) any).getName()
+                                        : ((AnyObject) any).getName()));
             } catch (Exception e) {
                 LOG.warn("Failure pushing '{}' on '{}'", any, resource, e);
                 throw new JobExecutionException("While pushing " + any + " on " + resource, e);
@@ -185,8 +228,12 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
             }
         }
 
+        status.set("Initialization completed");
+
         // First realms...
         if (pushTask.getResource().getOrgUnit() != null) {
+            status.set("Pushing realms");
+
             rhandler = buildRealmHandler();
 
             for (Realm realm : realmDAO.findDescendants(profile.getTask().getSourceRealm())) {
@@ -194,6 +241,7 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
                 if (realm.getParent() != null) {
                     try {
                         rhandler.handle(realm.getKey());
+                        reportHandled(SyncopeConstants.REALM_ANYTYPE, realm.getName());
                     } catch (Exception e) {
                         LOG.warn("Failure pushing '{}' on '{}'", realm, pushTask.getResource(), e);
                         throw new JobExecutionException("While pushing " + realm + " on " + pushTask.getResource(), e);
@@ -209,6 +257,8 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
 
         for (Provision provision : pushTask.getResource().getProvisions()) {
             if (provision.getMapping() != null) {
+                status.set("Pushing " + provision.getAnyType().getKey());
+
                 AnyDAO<?> anyDAO = getAnyDAO(provision.getAnyType().getKind());
 
                 SyncopePushResultHandler handler;
@@ -255,6 +305,8 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
             }
         }
 
+        status.set("Push done");
+
         String result = createReport(profile.getResults(), pushTask.getResource(), dryRun);
         LOG.debug("Push result: {}", result);
         return result;

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractExecutableService.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractExecutableService.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractExecutableService.java
index aadbaf3..81ef47c 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractExecutableService.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractExecutableService.java
@@ -71,6 +71,11 @@ public abstract class AbstractExecutableService extends AbstractServiceImpl impl
     }
 
     @Override
+    public JobTO getJob(final String key) {
+        return getExecutableLogic().getJob(key);
+    }
+
+    @Override
     public List<JobTO> listJobs() {
         return getExecutableLogic().listJobs();
     }


[06/11] syncope git commit: [SYNCOPE-1279] Now providing runtime status updates from running Tasks and Reports

Posted by il...@apache.org.
[SYNCOPE-1279] Now providing runtime status updates from running Tasks and Reports


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/799f079f
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/799f079f
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/799f079f

Branch: refs/heads/2_0_X
Commit: 799f079f132d6a2f9dc36a0bfd20475eb988febc
Parents: 988dfee
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Fri Mar 2 11:45:34 2018 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Mar 2 11:45:34 2018 +0100

----------------------------------------------------------------------
 .../console/reports/ReportDirectoryPanel.java   |  56 ++++
 .../console/rest/AnyObjectRestClient.java       |   2 -
 .../client/console/rest/ReportRestClient.java   |   4 +
 .../client/console/rest/TaskRestClient.java     |   4 +
 .../tasks/ProvisioningTaskDirectoryPanel.java   |  61 ++++
 .../client/console/widgets/JobActionPanel.java  |  23 +-
 .../client/console/widgets/JobWidget.java       |   4 +-
 .../META-INF/resources/css/syncopeConsole.css   |   6 +-
 .../client/console/widgets/JobActionPanel.html  |   2 +
 .../org/apache/syncope/common/lib/to/JobTO.java |  11 +
 .../rest/api/service/ExecutableService.java     |  11 +
 .../core/logic/AbstractExecutableLogic.java     |   2 +
 .../syncope/core/logic/AbstractJobLogic.java    |  63 ++--
 .../apache/syncope/core/logic/ReportLogic.java  |  25 ++
 .../apache/syncope/core/logic/TaskLogic.java    |  27 +-
 .../core/persistence/api/dao/Reportlet.java     |   4 +-
 .../core/provisioning/api/Connector.java        |   7 +-
 .../core/provisioning/api/job/JobDelegate.java  |  27 ++
 .../api/job/SchedTaskJobDelegate.java           |   2 +-
 .../api/job/report/ReportJobDelegate.java       |  27 ++
 .../notification/NotificationJobDelegate.java   |  31 ++
 .../api/pushpull/SyncopePullExecutor.java       |   3 +
 .../provisioning/java/ConnectorFacadeProxy.java |  13 +-
 .../java/job/AbstractInterruptableJob.java      |  21 +-
 .../java/job/AbstractSchedTaskJobDelegate.java  |  12 +
 .../GroupMemberProvisionTaskJobDelegate.java    |  28 +-
 .../java/job/IdentityRecertification.java       |   9 +-
 .../core/provisioning/java/job/TaskJob.java     |  19 +-
 .../DefaultNotificationJobDelegate.java         | 296 +++++++++++++++++++
 .../java/job/notification/NotificationJob.java  |   7 +
 .../notification/NotificationJobDelegate.java   | 278 -----------------
 .../java/job/report/AbstractReportlet.java      |  13 +-
 .../java/job/report/AuditReportlet.java         |  16 +-
 .../job/report/DefaultReportJobDelegate.java    | 216 ++++++++++++++
 .../java/job/report/GroupReportlet.java         |  19 +-
 .../job/report/ReconciliationReportlet.java     | 106 +++++--
 .../provisioning/java/job/report/ReportJob.java |   7 +
 .../java/job/report/ReportJobDelegate.java      | 197 ------------
 .../java/job/report/StaticReportlet.java        |   8 +-
 .../java/job/report/UserReportlet.java          |  17 +-
 .../AbstractPropagationTaskExecutor.java        |  11 +-
 .../pushpull/AbstractPullResultHandler.java     |   2 +
 .../pushpull/DefaultRealmPullResultHandler.java |   2 +
 .../java/pushpull/PullJobDelegate.java          |  40 +++
 .../java/pushpull/PushJobDelegate.java          |  52 ++++
 .../cxf/service/AbstractExecutableService.java  |   5 +
 46 files changed, 1215 insertions(+), 581 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
index 01f77db..e0c5d6f 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
@@ -34,27 +34,36 @@ import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.panels.DirectoryPanel;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.rest.ReportRestClient;
+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;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.console.widgets.JobActionPanel;
 import org.apache.syncope.client.console.wizards.AjaxWizard;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.to.ReportTO;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
 import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
 import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.model.CompoundPropertyModel;
 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;
 
 /**
  * Reports page.
@@ -76,6 +85,17 @@ public abstract class ReportDirectoryPanel
         modal.size(Modal.Size.Large);
         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 ReportStartAtTogglePanel(container, pageRef);
         addInnerObject(startAt);
     }
@@ -107,10 +127,46 @@ public abstract class ReportDirectoryPanel
         columns.add(new BooleanPropertyColumn<ReportTO>(
                 new StringResourceModel("active", this), "active", "active"));
 
+        columns.add(new AbstractColumn<ReportTO, String>(new Model<>(""), "running") {
+
+            private static final long serialVersionUID = 4209532514416998046L;
+
+            @Override
+            public void populateItem(
+                    final Item<ICellPopulator<ReportTO>> cellItem,
+                    final String componentId,
+                    final IModel<ReportTO> rowModel) {
+
+                JobTO jobTO = restClient.getJob(rowModel.getObject().getKey());
+                JobActionPanel panel = new JobActionPanel(
+                        componentId, jobTO, false, ReportDirectoryPanel.this, pageRef);
+                MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE,
+                        String.format("%s,%s",
+                                StandardEntitlement.TASK_EXECUTE,
+                                StandardEntitlement.TASK_UPDATE));
+                cellItem.add(panel);
+            }
+
+            @Override
+            public String getCssClass() {
+                return "col-xs-1";
+            }
+        });
+
         return columns;
     }
 
     @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof JobActionPanel.JobActionPayload) {
+            container.modelChanged();
+            JobActionPanel.JobActionPayload.class.cast(event.getPayload()).getTarget().add(container);
+        } else {
+            super.onEvent(event);
+        }
+    }
+
+    @Override
     public ActionsPanel<ReportTO> getActions(final IModel<ReportTO> model) {
         final ActionsPanel<ReportTO> panel = super.getActions(model);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java
index 31cd3a8..a885764 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java
@@ -18,8 +18,6 @@
  */
 package org.apache.syncope.client.console.rest;
 
-import static org.apache.syncope.client.console.rest.BaseRestClient.getService;
-
 import java.util.List;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.Response;

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java
index 0c96627..e65ce7e 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java
@@ -54,6 +54,10 @@ public class ReportRestClient extends BaseRestClient
         return getService(ReportService.class).list();
     }
 
+    public JobTO getJob(final String key) {
+        return getService(ReportService.class).getJob(key);
+    }
+
     public List<JobTO> listJobs() {
         return getService(ReportService.class).listJobs();
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
index d184085..723ae2e 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
@@ -47,6 +47,10 @@ public class TaskRestClient extends BaseRestClient implements ExecutionRestClien
 
     private static final long serialVersionUID = 6284485820911028843L;
 
+    public JobTO getJob(final String key) {
+        return getService(TaskService.class).getJob(key);
+    }
+
     public List<JobTO> listJobs() {
         return getService(TaskService.class).listJobs();
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
index 91f17b8..cacd6f7 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
@@ -23,18 +23,32 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
+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;
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.widgets.JobActionPanel;
+import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.to.ProvisioningTaskTO;
 import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.markup.html.WebPage;
+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.
@@ -63,6 +77,17 @@ public abstract class ProvisioningTaskDirectoryPanel<T extends ProvisioningTaskT
 
         // super in order to call the parent implementation
         super.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);
+            }
+        });
     }
 
     @Override
@@ -103,9 +128,45 @@ public abstract class ProvisioningTaskDirectoryPanel<T extends ProvisioningTaskT
         columns.add(new BooleanPropertyColumn<T>(
                 new StringResourceModel("active", this), "active", "active"));
 
+        columns.add(new AbstractColumn<T, String>(new Model<>(""), "running") {
+
+            private static final long serialVersionUID = -4008579357070833846L;
+
+            @Override
+            public void populateItem(
+                    final Item<ICellPopulator<T>> cellItem,
+                    final String componentId,
+                    final IModel<T> rowModel) {
+
+                JobTO jobTO = restClient.getJob(rowModel.getObject().getKey());
+                JobActionPanel panel = new JobActionPanel(
+                        componentId, jobTO, false, ProvisioningTaskDirectoryPanel.this, pageRef);
+                MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE,
+                        String.format("%s,%s",
+                                StandardEntitlement.TASK_EXECUTE,
+                                StandardEntitlement.TASK_UPDATE));
+                cellItem.add(panel);
+            }
+
+            @Override
+            public String getCssClass() {
+                return "col-xs-1";
+            }
+        });
+
         return columns;
     }
 
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof JobActionPanel.JobActionPayload) {
+            container.modelChanged();
+            JobActionPanel.JobActionPayload.class.cast(event.getPayload()).getTarget().add(container);
+        } else {
+            super.onEvent(event);
+        }
+    }
+
     protected class ProvisioningTasksProvider<T extends ProvisioningTaskTO> extends SchedTasksProvider<T> {
 
         private static final long serialVersionUID = 4725679400450513556L;

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
index 82665f8..5e03e2d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
@@ -18,6 +18,9 @@
  */
 package org.apache.syncope.client.console.widgets;
 
+import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverBehavior;
+import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverConfig;
+import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig;
 import java.io.Serializable;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
@@ -30,10 +33,13 @@ import org.apache.syncope.client.console.wicket.ajax.markup.html.IndicatorAjaxLi
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
 import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.types.JobAction;
+import org.apache.wicket.Component;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.model.Model;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,14 +58,21 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> {
     public JobActionPanel(
             final String id,
             final JobTO jobTO,
-            final JobWidget widget,
+            final boolean showNotRunning,
+            final Component container,
             final PageReference pageRef) {
+
         super(id, true);
         setOutputMarkupId(true);
 
         Fragment controls;
         if (jobTO.isRunning()) {
             controls = new Fragment("controls", "runningFragment", this);
+            controls.add(new Label("status", Model.of()).add(new PopoverBehavior(
+                    Model.<String>of(),
+                    Model.of("<pre>" + (jobTO.getStatus() == null ? StringUtils.EMPTY : jobTO.getStatus()) + "</pre>"),
+                    new PopoverConfig().withAnimation(true).withHoverTrigger().withHtml(true).
+                            withPlacement(TooltipConfig.Placement.left))));
             controls.add(new IndicatorAjaxLink<Void>("stop") {
 
                 private static final long serialVersionUID = -7978723352517770644L;
@@ -83,7 +96,7 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> {
                             default:
                         }
                         SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
-                        send(widget, Broadcast.EXACT, new JobActionPayload(target));
+                        send(container, Broadcast.EXACT, new JobActionPayload(target));
                     } catch (Exception e) {
                         LOG.error("While stopping {}", jobTO.getRefDesc(), e);
                         SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName()
@@ -117,7 +130,7 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> {
                             default:
                         }
                         SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
-                        send(widget, Broadcast.EXACT, new JobActionPayload(target));
+                        send(container, Broadcast.EXACT, new JobActionPayload(target));
                     } catch (Exception e) {
                         LOG.error("While starting {}", jobTO.getRefDesc(), e);
                         SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName()
@@ -126,6 +139,10 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> {
                     ((BasePage) getPage()).getNotificationPanel().refresh(target);
                 }
             });
+            if (!showNotRunning) {
+                controls.setOutputMarkupPlaceholderTag(true);
+                controls.setVisible(false);
+            }
         }
         addInnerObject(controls);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
index cd10c70..98a53e4 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
@@ -357,9 +357,9 @@ public class JobWidget extends BaseWidget {
                         final IModel<JobTO> rowModel) {
 
                     JobTO jobTO = rowModel.getObject();
-                    JobActionPanel panel = new JobActionPanel(componentId, jobTO, JobWidget.this, pageRef);
+                    JobActionPanel panel = new JobActionPanel(componentId, jobTO, true, JobWidget.this, pageRef);
                     MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE,
-                            String.format("%s,%s%s,%s",
+                            String.format("%s,%s,%s,%s",
                                     StandardEntitlement.TASK_EXECUTE,
                                     StandardEntitlement.REPORT_EXECUTE,
                                     StandardEntitlement.TASK_UPDATE,

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
index 294c978..c2dcf2c 100644
--- a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
+++ b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
@@ -879,6 +879,10 @@ li.todoitem a {
   cursor: default;
 }
 
+.popover{
+    max-width: 100%;
+}
+
 #popover:hover {
   cursor: pointer;
 }
@@ -1157,4 +1161,4 @@ div#inline-actions ul.menu i, div#tablehandling ul.menu i {
 
 div#tablehandling ul.menu li a {
   padding: 0px !important;
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html
index e94f292..8e31f36 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html
@@ -22,6 +22,8 @@ under the License.
     <wicket:fragment wicket:id="runningFragment">
       <i id="actionLink" class="fa fa-refresh fa-spin"></i>
       &nbsp;
+      <div wicket:id="status" class="fa fa-binoculars"/>
+      &nbsp;
       <a href="#" wicket:id="stop" class="fa fa-stop-circle"></a>
     </wicket:fragment>
     <wicket:fragment wicket:id="notRunningFragment">

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java
index 2cb5690..cee8db2 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java
@@ -42,6 +42,8 @@ public class JobTO extends AbstractBaseBean {
 
     private Date start;
 
+    private String status;
+
     public JobType getType() {
         return type;
     }
@@ -93,4 +95,13 @@ public class JobTO extends AbstractBaseBean {
                 ? null
                 : new Date(start.getTime());
     }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(final String status) {
+        this.status = status;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java
index e7271b2..37301bd 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java
@@ -101,6 +101,17 @@ public interface ExecutableService extends JAXRSService {
     ExecTO execute(@BeanParam ExecuteQuery query);
 
     /**
+     * Returns job (running or scheduled) for the executable matching the given key.
+     *
+     * @param key executable key
+     * @return job (running or scheduled) for the given key
+     */
+    @GET
+    @Path("jobs/{key}")
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    JobTO getJob(@PathParam("key") String key);
+
+    /**
      * List jobs (running and / or scheduled).
      *
      * @return jobs (running and / or scheduled)

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java
index d1b7cb3..6b34bc7 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java
@@ -42,6 +42,8 @@ public abstract class AbstractExecutableLogic<T extends AbstractBaseBean> extend
     public abstract BulkActionResult deleteExecutions(
             String key, Date startedBefore, Date startedAfter, Date endedBefore, Date endedAfter);
 
+    public abstract JobTO getJob(String key);
+
     public abstract List<JobTO> listJobs();
 
     public abstract void actionJob(String key, JobAction action);

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
index a93ae2d..844d353 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
@@ -26,11 +26,14 @@ import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.types.JobAction;
 import org.apache.syncope.common.lib.types.JobType;
 import org.apache.syncope.core.provisioning.api.job.JobManager;
+import org.apache.syncope.core.provisioning.java.job.AbstractInterruptableJob;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.quartz.JobKey;
 import org.quartz.Scheduler;
 import org.quartz.SchedulerException;
 import org.quartz.Trigger;
 import org.quartz.impl.matchers.GroupMatcher;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 
@@ -44,32 +47,54 @@ abstract class AbstractJobLogic<T extends AbstractBaseBean> extends AbstractTran
 
     protected abstract Triple<JobType, String, String> getReference(final JobKey jobKey);
 
-    protected List<JobTO> doListJobs() {
-        List<JobTO> jobTOs = new ArrayList<>();
+    protected JobTO getJobTO(final JobKey jobKey) throws SchedulerException {
+        JobTO jobTO = null;
 
-        try {
-            for (JobKey jobKey : scheduler.getScheduler().
-                    getJobKeys(GroupMatcher.jobGroupEquals(Scheduler.DEFAULT_GROUP))) {
+        Triple<JobType, String, String> reference = getReference(jobKey);
+        if (reference != null) {
+            jobTO = new JobTO();
 
-                JobTO jobTO = new JobTO();
+            jobTO.setType(reference.getLeft());
+            jobTO.setRefKey(reference.getMiddle());
+            jobTO.setRefDesc(reference.getRight());
 
-                Triple<JobType, String, String> reference = getReference(jobKey);
-                if (reference != null) {
-                    jobTOs.add(jobTO);
+            List<? extends Trigger> jobTriggers = scheduler.getScheduler().getTriggersOfJob(jobKey);
+            if (jobTriggers.isEmpty()) {
+                jobTO.setScheduled(false);
+            } else {
+                jobTO.setScheduled(true);
+                jobTO.setStart(jobTriggers.get(0).getStartTime());
+            }
+
+            jobTO.setRunning(jobManager.isRunning(jobKey));
 
-                    jobTO.setType(reference.getLeft());
-                    jobTO.setRefKey(reference.getMiddle());
-                    jobTO.setRefDesc(reference.getRight());
+            jobTO.setStatus("UNKNOWN");
+            if (jobTO.isRunning()) {
+                try {
+                    Object job = ApplicationContextProvider.getBeanFactory().getBean(jobKey.getName());
+                    if (job instanceof AbstractInterruptableJob
+                            && ((AbstractInterruptableJob) job).getDelegate() != null) {
 
-                    List<? extends Trigger> jobTriggers = scheduler.getScheduler().getTriggersOfJob(jobKey);
-                    if (jobTriggers.isEmpty()) {
-                        jobTO.setScheduled(false);
-                    } else {
-                        jobTO.setScheduled(true);
-                        jobTO.setStart(jobTriggers.get(0).getStartTime());
+                        jobTO.setStatus(((AbstractInterruptableJob) job).getDelegate().currentStatus());
                     }
+                } catch (NoSuchBeanDefinitionException e) {
+                    LOG.warn("Could not find job {} implementation", jobKey, e);
+                }
+            }
+        }
+
+        return jobTO;
+    }
+
+    protected List<JobTO> doListJobs() {
+        List<JobTO> jobTOs = new ArrayList<>();
+        try {
+            for (JobKey jobKey : scheduler.getScheduler().
+                    getJobKeys(GroupMatcher.jobGroupEquals(Scheduler.DEFAULT_GROUP))) {
 
-                    jobTO.setRunning(jobManager.isRunning(jobKey));
+                JobTO jobTO = getJobTO(jobKey);
+                if (jobTO != null) {
+                    jobTOs.add(jobTO);
                 }
             }
         } catch (SchedulerException e) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
index 9505303..da8f2b7 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
@@ -66,6 +66,7 @@ import org.apache.syncope.core.provisioning.api.data.ReportDataBinder;
 import org.apache.syncope.core.provisioning.api.job.JobNamer;
 import org.apache.xmlgraphics.util.MimeConstants;
 import org.quartz.JobKey;
+import org.quartz.SchedulerException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
@@ -387,6 +388,30 @@ public class ReportLogic extends AbstractExecutableLogic<ReportTO> {
         return super.doListJobs();
     }
 
+    @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_READ + "')")
+    @Override
+    public JobTO getJob(final String key) {
+        Report report = reportDAO.find(key);
+        if (report == null) {
+            throw new NotFoundException("Report " + key);
+        }
+
+        JobTO jobTO = null;
+        try {
+            jobTO = getJobTO(JobNamer.getJobKey(report));
+        } catch (SchedulerException e) {
+            LOG.error("Problems while retrieving scheduled job {}", JobNamer.getJobKey(report), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+        if (jobTO == null) {
+            throw new NotFoundException("Job for report " + key);
+        }
+        return jobTO;
+    }
+
     @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_EXECUTE + "')")
     @Override
     public void actionJob(final String key, final JobAction action) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
index 9603bc8..25f06a1 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
@@ -57,10 +57,11 @@ import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecu
 import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.NotificationDAO;
+import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate;
 import org.apache.syncope.core.provisioning.java.job.TaskJob;
-import org.apache.syncope.core.provisioning.java.job.notification.NotificationJobDelegate;
 import org.quartz.JobDataMap;
 import org.quartz.JobKey;
+import org.quartz.SchedulerException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
@@ -402,6 +403,30 @@ public class TaskLogic extends AbstractExecutableLogic<TaskTO> {
         return super.doListJobs();
     }
 
+    @PreAuthorize("hasRole('" + StandardEntitlement.TASK_READ + "')")
+    @Override
+    public JobTO getJob(final String key) {
+        Task task = taskDAO.find(key);
+        if (task == null) {
+            throw new NotFoundException("Task " + key);
+        }
+
+        JobTO jobTO = null;
+        try {
+            jobTO = getJobTO(JobNamer.getJobKey(task));
+        } catch (SchedulerException e) {
+            LOG.error("Problems while retrieving scheduled job {}", JobNamer.getJobKey(task), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+        if (jobTO == null) {
+            throw new NotFoundException("Job for task " + key);
+        }
+        return jobTO;
+    }
+
     @PreAuthorize("hasRole('" + StandardEntitlement.TASK_EXECUTE + "')")
     @Override
     public void actionJob(final String key, final JobAction action) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java
index 8180831..11e6b25 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.persistence.api.dao;
 
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.common.lib.report.ReportletConf;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
@@ -34,7 +35,8 @@ public interface Reportlet {
      *
      * @param conf configuration
      * @param handler SAX content handler for streaming result
+     * @param status current report status (for job reporting)
      * @throws SAXException if there is any problem in SAX handling
      */
-    void extract(ReportletConf conf, ContentHandler handler) throws SAXException;
+    void extract(ReportletConf conf, ContentHandler handler, AtomicReference<String> status) throws SAXException;
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
index 901cdc7..2098466 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.api;
 
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.identityconnectors.framework.common.objects.Attribute;
@@ -63,7 +64,7 @@ public interface Connector {
             ObjectClass objectClass,
             Set<Attribute> attrs,
             OperationOptions options,
-            Boolean[] propagationAttempted);
+            AtomicReference<Boolean> propagationAttempted);
 
     /**
      * Update user / group on a connector instance.
@@ -80,7 +81,7 @@ public interface Connector {
             Uid uid,
             Set<Attribute> attrs,
             OperationOptions options,
-            Boolean[] propagationAttempted);
+            AtomicReference<Boolean> propagationAttempted);
 
     /**
      * Delete user / group on a connector instance.
@@ -94,7 +95,7 @@ public interface Connector {
             ObjectClass objectClass,
             Uid uid,
             OperationOptions options,
-            Boolean[] propagationAttempted);
+            AtomicReference<Boolean> propagationAttempted);
 
     /**
      * Fetches all remote objects (for use during full reconciliation).

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java
new file mode 100644
index 0000000..3bfa292
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java
@@ -0,0 +1,27 @@
+/*
+ * 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.core.provisioning.api.job;
+
+/**
+ * Implementations of this interface will perform the actual operations required to Quartz's {@link org.quartz.Job}.
+ */
+public interface JobDelegate {
+
+    String currentStatus();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java
index a03f36b..bb69b10 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java
@@ -21,7 +21,7 @@ package org.apache.syncope.core.provisioning.api.job;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 
-public interface SchedTaskJobDelegate {
+public interface SchedTaskJobDelegate extends JobDelegate {
 
     void execute(String taskKey, boolean dryRun, JobExecutionContext context) throws JobExecutionException;
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java
new file mode 100644
index 0000000..bbf455f
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java
@@ -0,0 +1,27 @@
+/*
+ * 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.core.provisioning.api.job.report;
+
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
+import org.quartz.JobExecutionException;
+
+public interface ReportJobDelegate extends JobDelegate {
+
+    void execute(String reportKey) throws JobExecutionException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java
new file mode 100644
index 0000000..3dfcddd
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java
@@ -0,0 +1,31 @@
+/*
+ * 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.core.provisioning.api.notification;
+
+import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
+import org.quartz.JobExecutionException;
+
+public interface NotificationJobDelegate extends JobDelegate {
+
+    TaskExec executeSingle(NotificationTask task);
+
+    void execute() throws JobExecutionException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java
index ab02282..39eed32 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java
@@ -18,10 +18,13 @@
  */
 package org.apache.syncope.core.provisioning.api.pushpull;
 
+import org.identityconnectors.framework.common.objects.Name;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.identityconnectors.framework.common.objects.SyncToken;
 
 public interface SyncopePullExecutor {
 
     void setLatestSyncToken(ObjectClass objectClass, SyncToken latestSyncToken);
+
+    void reportHandled(ObjectClass objectClass, Name name);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
index 7e94c5b..aea4e27 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.Transformer;
 import org.apache.syncope.common.lib.types.ConnConfProperty;
@@ -165,12 +166,12 @@ public class ConnectorFacadeProxy implements Connector {
             final ObjectClass objectClass,
             final Set<Attribute> attrs,
             final OperationOptions options,
-            final Boolean[] propagationAttempted) {
+            final AtomicReference<Boolean> propagationAttempted) {
 
         Uid result = null;
 
         if (connInstance.getCapabilities().contains(ConnectorCapability.CREATE)) {
-            propagationAttempted[0] = true;
+            propagationAttempted.set(true);
 
             Future<Uid> future = asyncFacade.create(connector, objectClass, attrs, options);
             try {
@@ -200,12 +201,12 @@ public class ConnectorFacadeProxy implements Connector {
             final Uid uid,
             final Set<Attribute> attrs,
             final OperationOptions options,
-            final Boolean[] propagationAttempted) {
+            final AtomicReference<Boolean> propagationAttempted) {
 
         Uid result = null;
 
         if (connInstance.getCapabilities().contains(ConnectorCapability.UPDATE)) {
-            propagationAttempted[0] = true;
+            propagationAttempted.set(true);
 
             Future<Uid> future = asyncFacade.update(connector, objectClass, uid, attrs, options);
 
@@ -236,10 +237,10 @@ public class ConnectorFacadeProxy implements Connector {
             final ObjectClass objectClass,
             final Uid uid,
             final OperationOptions options,
-            final Boolean[] propagationAttempted) {
+            final AtomicReference<Boolean> propagationAttempted) {
 
         if (connInstance.getCapabilities().contains(ConnectorCapability.DELETE)) {
-            propagationAttempted[0] = true;
+            propagationAttempted.set(true);
 
             Future<Uid> future = asyncFacade.delete(connector, objectClass, uid, options);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java
index 19bbf1e..86a7f49 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.java.job;
 
 import java.util.Date;
 import java.util.concurrent.atomic.AtomicReference;
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
 import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
 import org.apache.syncope.core.provisioning.api.job.JobManager;
 import org.quartz.DisallowConcurrentExecution;
@@ -40,13 +41,25 @@ public abstract class AbstractInterruptableJob implements InterruptableJob {
      */
     private final AtomicReference<Thread> runningThread = new AtomicReference<>();
 
+    private final JobDelegate embeddedDelegate = new JobDelegate() {
+
+        @Override
+        public String currentStatus() {
+            return "RUNNING THREAD: " + runningThread.get();
+        }
+    };
+
     private long interruptMaxRetries = 1;
 
+    public JobDelegate getDelegate() {
+        return embeddedDelegate;
+    }
+
     @Override
     public void execute(final JobExecutionContext context) throws JobExecutionException {
-        this.runningThread.set(Thread.currentThread());
+        runningThread.set(Thread.currentThread());
         try {
-            this.interruptMaxRetries = context.getMergedJobDataMap().getLong(JobManager.INTERRUPT_MAX_RETRIES_KEY);
+            interruptMaxRetries = context.getMergedJobDataMap().getLong(JobManager.INTERRUPT_MAX_RETRIES_KEY);
         } catch (Exception e) {
             LOG.debug("Could not set {}, defaults to {}", JobManager.INTERRUPT_MAX_RETRIES_KEY, interruptMaxRetries, e);
         }
@@ -54,7 +67,7 @@ public abstract class AbstractInterruptableJob implements InterruptableJob {
 
     @Override
     public void interrupt() throws UnableToInterruptJobException {
-        Thread thread = this.runningThread.getAndSet(null);
+        Thread thread = runningThread.getAndSet(null);
         if (thread == null) {
             LOG.warn("Unable to retrieve the thread of the current job execution");
         } else {
@@ -68,7 +81,7 @@ public abstract class AbstractInterruptableJob implements InterruptableJob {
             }
             // if the thread is still alive, it should be available in the next stop
             if (thread.isAlive()) {
-                this.runningThread.set(thread);
+                runningThread.set(thread);
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java
index 5fc5405..7103b7b 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.provisioning.java.job;
 
 import java.util.Date;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
 import org.apache.syncope.core.persistence.api.dao.TaskDAO;
@@ -72,6 +73,13 @@ public abstract class AbstractSchedTaskJobDelegate implements SchedTaskJobDelega
     @Autowired
     protected AuditManager auditManager;
 
+    protected final AtomicReference<String> status = new AtomicReference<>();
+
+    @Override
+    public String currentStatus() {
+        return status.get();
+    }
+
     @Transactional
     @Override
     public void execute(final String taskKey, final boolean dryRun, final JobExecutionContext context)
@@ -90,6 +98,8 @@ public abstract class AbstractSchedTaskJobDelegate implements SchedTaskJobDelega
         execution.setStart(new Date());
         execution.setTask(task);
 
+        status.set("Initialization completed");
+
         AuditElements.Result result;
 
         try {
@@ -110,6 +120,8 @@ public abstract class AbstractSchedTaskJobDelegate implements SchedTaskJobDelega
         }
         task = taskDAO.save(task);
 
+        status.set("Done");
+
         notificationManager.createTasks(
                 AuditElements.EventCategoryType.TASK,
                 this.getClass().getSimpleName(),

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
index 4868bbc..4f1a1eb 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
@@ -82,20 +82,25 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
         }
         result.append("provision\n\n");
 
+        status.set(result.toString());
+
         MembershipCond membershipCond = new MembershipCond();
         membershipCond.setGroup(groupKey);
         List<User> users = searchDAO.search(SearchCond.getLeafCond(membershipCond), AnyTypeKind.USER);
         Collection<String> groupResourceKeys = groupDAO.findAllResourceKeys(groupKey);
+        status.set("About to "
+                + (actionType == BulkMembersActionType.DEPROVISION ? "de" : "") + "provision "
+                + users.size() + " users from " + groupResourceKeys);
         for (User user : users) {
             List<PropagationStatus> statuses = actionType == BulkMembersActionType.DEPROVISION
                     ? userProvisioningManager.deprovision(user.getKey(), groupResourceKeys, false)
                     : userProvisioningManager.provision(user.getKey(), true, null, groupResourceKeys, false);
-            for (PropagationStatus status : statuses) {
+            for (PropagationStatus propagationStatus : statuses) {
                 result.append("User ").append(user.getKey()).append('\t').
-                        append("Resource ").append(status.getResource()).append('\t').
-                        append(status.getStatus());
-                if (StringUtils.isNotBlank(status.getFailureReason())) {
-                    result.append('\n').append(status.getFailureReason()).append('\n');
+                        append("Resource ").append(propagationStatus.getResource()).append('\t').
+                        append(propagationStatus.getStatus());
+                if (StringUtils.isNotBlank(propagationStatus.getFailureReason())) {
+                    result.append('\n').append(propagationStatus.getFailureReason()).append('\n');
                 }
                 result.append("\n");
             }
@@ -105,17 +110,20 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
         membershipCond = new MembershipCond();
         membershipCond.setGroup(groupKey);
         List<AnyObject> anyObjects = searchDAO.search(SearchCond.getLeafCond(membershipCond), AnyTypeKind.ANY_OBJECT);
+        status.set("About to "
+                + (actionType == BulkMembersActionType.DEPROVISION ? "de" : "") + "provision "
+                + anyObjects.size() + " any objects from " + groupResourceKeys);
         for (AnyObject anyObject : anyObjects) {
             List<PropagationStatus> statuses = actionType == BulkMembersActionType.DEPROVISION
                     ? anyObjectProvisioningManager.deprovision(anyObject.getKey(), groupResourceKeys, false)
                     : anyObjectProvisioningManager.provision(anyObject.getKey(), groupResourceKeys, false);
 
-            for (PropagationStatus status : statuses) {
+            for (PropagationStatus propagationStatus : statuses) {
                 result.append(anyObject.getType().getKey()).append(' ').append(anyObject.getKey()).append('\t').
-                        append("Resource ").append(status.getResource()).append('\t').
-                        append(status.getStatus());
-                if (StringUtils.isNotBlank(status.getFailureReason())) {
-                    result.append('\n').append(status.getFailureReason()).append('\n');
+                        append("Resource ").append(propagationStatus.getResource()).append('\t').
+                        append(propagationStatus.getStatus());
+                if (StringUtils.isNotBlank(propagationStatus.getFailureReason())) {
+                    result.append('\n').append(propagationStatus.getFailureReason()).append('\n');
                 }
                 result.append("\n");
             }

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
index 8b3f4e5..332af19 100755
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
@@ -87,8 +87,15 @@ public class IdentityRecertification extends AbstractSchedTaskJobDelegate {
             return "DRY RUN";
         }
 
+        int total = userDAO.count();
+        int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+        status.set("Processing " + total + " users in " + pages + " pages");
+
         long now = System.currentTimeMillis();
-        for (int page = 1; page <= (userDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+        for (int page = 1; page <= pages; page++) {
+            status.set("Processing " + total + " users: page " + page + " of " + pages);
+
             for (User user : userDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE)) {
                 LOG.debug("Processing user: {}", user.getUsername());
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
index 041d3b4..d688179 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.provisioning.java.job;
 
 import org.apache.commons.lang3.ClassUtils;
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate;
@@ -52,6 +53,8 @@ public class TaskJob extends AbstractInterruptableJob {
      */
     private String taskKey;
 
+    private SchedTaskJobDelegate delegate;
+
     /**
      * Task key setter.
      *
@@ -62,6 +65,11 @@ public class TaskJob extends AbstractInterruptableJob {
     }
 
     @Override
+    public JobDelegate getDelegate() {
+        return delegate;
+    }
+
+    @Override
     public void execute(final JobExecutionContext context) throws JobExecutionException {
         super.execute(context);
 
@@ -75,11 +83,12 @@ public class TaskJob extends AbstractInterruptableJob {
                         Class<?> delegateClass =
                                 ClassUtils.getClass(context.getMergedJobDataMap().getString(DELEGATE_CLASS_KEY));
 
-                        ((SchedTaskJobDelegate) ApplicationContextProvider.getBeanFactory().
-                                createBean(delegateClass, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false)).
-                                execute(taskKey,
-                                        context.getMergedJobDataMap().getBoolean(DRY_RUN_JOBDETAIL_KEY),
-                                        context);
+                        delegate = ((SchedTaskJobDelegate) ApplicationContextProvider.getBeanFactory().
+                                createBean(delegateClass, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false));
+                        delegate.execute(
+                                taskKey,
+                                context.getMergedJobDataMap().getBoolean(DRY_RUN_JOBDETAIL_KEY),
+                                context);
                     } catch (Exception e) {
                         LOG.error("While executing task {}", taskKey, e);
                         throw new RuntimeException(e);

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java
new file mode 100644
index 0000000..7ab218b
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java
@@ -0,0 +1,296 @@
+/*
+ * 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.core.provisioning.java.job.notification;
+
+import java.io.PrintStream;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.LogOutputStream;
+import org.apache.syncope.common.lib.PropertyUtils;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.common.lib.types.TraceLevel;
+import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
+import org.apache.syncope.core.persistence.api.dao.TaskDAO;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.core.provisioning.api.AuditManager;
+import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate;
+import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+import org.apache.syncope.core.spring.security.Encryptor;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class DefaultNotificationJobDelegate implements InitializingBean, NotificationJobDelegate {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationJobDelegate.class);
+
+    @Autowired
+    private TaskDAO taskDAO;
+
+    @Autowired
+    private JavaMailSender mailSender;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Autowired
+    private AuditManager auditManager;
+
+    @Autowired
+    private NotificationManager notificationManager;
+
+    private final AtomicReference<String> status = new AtomicReference<>();
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        if (mailSender instanceof JavaMailSenderImpl) {
+            JavaMailSenderImpl javaMailSender = (JavaMailSenderImpl) mailSender;
+
+            Properties javaMailProperties = javaMailSender.getJavaMailProperties();
+
+            Properties props = PropertyUtils.read(Encryptor.class, "mail.properties", "conf.directory").getLeft();
+            for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) {
+                String prop = (String) e.nextElement();
+                if (prop.startsWith("mail.smtp.")) {
+                    javaMailProperties.setProperty(prop, props.getProperty(prop));
+                }
+            }
+
+            if (StringUtils.isNotBlank(javaMailSender.getUsername())) {
+                javaMailProperties.setProperty("mail.smtp.auth", "true");
+            }
+
+            javaMailSender.setJavaMailProperties(javaMailProperties);
+
+            String mailDebug = props.getProperty("mail.debug", "false");
+            if (BooleanUtils.toBoolean(mailDebug)) {
+                Session session = javaMailSender.getSession();
+                session.setDebug(true);
+                session.setDebugOut(new PrintStream(new LogOutputStream(LOG)));
+            }
+        }
+    }
+
+    @Override
+    public String currentStatus() {
+        return status.get();
+    }
+
+    @Transactional
+    @Override
+    public TaskExec executeSingle(final NotificationTask task) {
+        TaskExec execution = entityFactory.newEntity(TaskExec.class);
+        execution.setTask(task);
+        execution.setStart(new Date());
+
+        boolean retryPossible = true;
+
+        if (StringUtils.isBlank(task.getSubject()) || task.getRecipients().isEmpty()
+                || StringUtils.isBlank(task.getHtmlBody()) || StringUtils.isBlank(task.getTextBody())) {
+
+            String message = "Could not fetch all required information for sending e-mails:\n"
+                    + task.getRecipients() + "\n"
+                    + task.getSender() + "\n"
+                    + task.getSubject() + "\n"
+                    + task.getHtmlBody() + "\n"
+                    + task.getTextBody();
+            LOG.error(message);
+
+            execution.setStatus(NotificationJob.Status.NOT_SENT.name());
+            retryPossible = false;
+
+            if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
+                execution.setMessage(message);
+            }
+        } else {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("About to send e-mails:\n"
+                        + task.getRecipients() + "\n"
+                        + task.getSender() + "\n"
+                        + task.getSubject() + "\n"
+                        + task.getHtmlBody() + "\n"
+                        + task.getTextBody() + "\n");
+            }
+
+            status.set("Sending notifications to " + task.getRecipients());
+
+            for (String to : task.getRecipients()) {
+                try {
+                    MimeMessage message = mailSender.createMimeMessage();
+                    MimeMessageHelper helper = new MimeMessageHelper(message, true);
+                    helper.setTo(to);
+                    helper.setFrom(task.getSender());
+                    helper.setSubject(task.getSubject());
+                    helper.setText(task.getTextBody(), task.getHtmlBody());
+
+                    mailSender.send(message);
+
+                    execution.setStatus(NotificationJob.Status.SENT.name());
+
+                    StringBuilder report = new StringBuilder();
+                    switch (task.getTraceLevel()) {
+                        case ALL:
+                            report.append("FROM: ").append(task.getSender()).append('\n').
+                                    append("TO: ").append(to).append('\n').
+                                    append("SUBJECT: ").append(task.getSubject()).append('\n').append('\n').
+                                    append(task.getTextBody()).append('\n').append('\n').
+                                    append(task.getHtmlBody()).append('\n');
+                            break;
+
+                        case SUMMARY:
+                            report.append("E-mail sent to ").append(to).append('\n');
+                            break;
+
+                        case FAILURES:
+                        case NONE:
+                        default:
+                    }
+                    if (report.length() > 0) {
+                        execution.setMessage(report.toString());
+                    }
+
+                    notificationManager.createTasks(
+                            AuditElements.EventCategoryType.TASK,
+                            "notification",
+                            null,
+                            "send",
+                            AuditElements.Result.SUCCESS,
+                            null,
+                            null,
+                            task,
+                            "Successfully sent notification to " + to);
+                } catch (Exception e) {
+                    LOG.error("Could not send e-mail", e);
+
+                    execution.setStatus(NotificationJob.Status.NOT_SENT.name());
+                    if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
+                        execution.setMessage(ExceptionUtils2.getFullStackTrace(e));
+                    }
+
+                    notificationManager.createTasks(
+                            AuditElements.EventCategoryType.TASK,
+                            "notification",
+                            null,
+                            "send",
+                            AuditElements.Result.FAILURE,
+                            null,
+                            null,
+                            task,
+                            "Could not send notification to " + to, e);
+                }
+
+                execution.setEnd(new Date());
+            }
+        }
+
+        if (hasToBeRegistered(execution)) {
+            execution = notificationManager.storeExec(execution);
+            if (retryPossible
+                    && (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT)) {
+
+                handleRetries(execution);
+            }
+        } else {
+            notificationManager.setTaskExecuted(execution.getTask().getKey(), true);
+        }
+
+        return execution;
+    }
+
+    @Transactional
+    @Override
+    public void execute() throws JobExecutionException {
+        List<NotificationTask> tasks = taskDAO.<NotificationTask>findToExec(TaskType.NOTIFICATION);
+
+        status.set("Sending out " + tasks.size() + " notifications");
+
+        for (NotificationTask task : tasks) {
+            LOG.debug("Found notification task {} to be executed: starting...", task);
+            executeSingle(task);
+            LOG.debug("Notification task {} executed", task);
+        }
+    }
+
+    private boolean hasToBeRegistered(final TaskExec execution) {
+        NotificationTask task = (NotificationTask) execution.getTask();
+
+        // True if either failed and failures have to be registered, or if ALL
+        // has to be registered.
+        return (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT
+                && task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
+                || task.getTraceLevel() == TraceLevel.ALL;
+    }
+
+    private void handleRetries(final TaskExec execution) {
+        if (notificationManager.getMaxRetries() <= 0) {
+            return;
+        }
+
+        long failedExecutionsCount = notificationManager.countExecutionsWithStatus(
+                execution.getTask().getKey(), NotificationJob.Status.NOT_SENT.name());
+
+        if (failedExecutionsCount <= notificationManager.getMaxRetries()) {
+            LOG.debug("Execution of notification task {} will be retried [{}/{}]",
+                    execution.getTask(), failedExecutionsCount, notificationManager.getMaxRetries());
+            notificationManager.setTaskExecuted(execution.getTask().getKey(), false);
+
+            auditManager.audit(
+                    AuditElements.EventCategoryType.TASK,
+                    "notification",
+                    null,
+                    "retry",
+                    AuditElements.Result.SUCCESS,
+                    null,
+                    null,
+                    execution,
+                    "Notification task " + execution.getTask().getKey() + " will be retried");
+        } else {
+            LOG.error("Maximum number of retries reached for task {} - giving up", execution.getTask());
+
+            auditManager.audit(
+                    AuditElements.EventCategoryType.TASK,
+                    "notification",
+                    null,
+                    "retry",
+                    AuditElements.Result.FAILURE,
+                    null,
+                    null,
+                    execution,
+                    "Giving up retries on notification task " + execution.getTask().getKey());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/799f079f/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java
index 153a221..7edcce2 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java
@@ -20,6 +20,8 @@ package org.apache.syncope.core.provisioning.java.job.notification;
 
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.persistence.api.DomainsHolder;
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
+import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate;
 import org.apache.syncope.core.provisioning.java.job.AbstractInterruptableJob;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
@@ -54,6 +56,11 @@ public class NotificationJob extends AbstractInterruptableJob {
     private NotificationJobDelegate delegate;
 
     @Override
+    public JobDelegate getDelegate() {
+        return delegate;
+    }
+
+    @Override
     public void execute(final JobExecutionContext context) throws JobExecutionException {
         super.execute(context);
 


[10/11] syncope git commit: [SYNCOPE-1279] Now providing runtime status updates from running Tasks and Reports

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java
deleted file mode 100644
index c019639..0000000
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java
+++ /dev/null
@@ -1,278 +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.core.provisioning.java.job.notification;
-
-import java.io.PrintStream;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.Properties;
-import javax.mail.Session;
-import javax.mail.internet.MimeMessage;
-import org.apache.commons.lang3.BooleanUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.lib.LogOutputStream;
-import org.apache.syncope.common.lib.PropertyUtils;
-import org.apache.syncope.common.lib.types.AuditElements;
-import org.apache.syncope.common.lib.types.TaskType;
-import org.apache.syncope.common.lib.types.TraceLevel;
-import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
-import org.apache.syncope.core.persistence.api.dao.TaskDAO;
-import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
-import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
-import org.apache.syncope.core.provisioning.api.AuditManager;
-import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
-import org.apache.syncope.core.spring.security.Encryptor;
-import org.quartz.JobExecutionException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.mail.javamail.JavaMailSender;
-import org.springframework.mail.javamail.JavaMailSenderImpl;
-import org.springframework.mail.javamail.MimeMessageHelper;
-import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
-
-@Component
-public class NotificationJobDelegate implements InitializingBean {
-
-    private static final Logger LOG = LoggerFactory.getLogger(NotificationJobDelegate.class);
-
-    @Autowired
-    private TaskDAO taskDAO;
-
-    @Autowired
-    private JavaMailSender mailSender;
-
-    @Autowired
-    private EntityFactory entityFactory;
-
-    @Autowired
-    private AuditManager auditManager;
-
-    @Autowired
-    private NotificationManager notificationManager;
-
-    @Override
-    public void afterPropertiesSet() throws Exception {
-        if (mailSender instanceof JavaMailSenderImpl) {
-            JavaMailSenderImpl javaMailSender = (JavaMailSenderImpl) mailSender;
-
-            Properties javaMailProperties = javaMailSender.getJavaMailProperties();
-
-            Properties props = PropertyUtils.read(Encryptor.class, "mail.properties", "conf.directory").getLeft();
-            for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) {
-                String prop = (String) e.nextElement();
-                if (prop.startsWith("mail.smtp.")) {
-                    javaMailProperties.setProperty(prop, props.getProperty(prop));
-                }
-            }
-
-            if (StringUtils.isNotBlank(javaMailSender.getUsername())) {
-                javaMailProperties.setProperty("mail.smtp.auth", "true");
-            }
-
-            javaMailSender.setJavaMailProperties(javaMailProperties);
-
-            String mailDebug = props.getProperty("mail.debug", "false");
-            if (BooleanUtils.toBoolean(mailDebug)) {
-                Session session = javaMailSender.getSession();
-                session.setDebug(true);
-                session.setDebugOut(new PrintStream(new LogOutputStream(LOG)));
-            }
-        }
-    }
-
-    @Transactional
-    public TaskExec executeSingle(final NotificationTask task) {
-        TaskExec execution = entityFactory.newEntity(TaskExec.class);
-        execution.setTask(task);
-        execution.setStart(new Date());
-
-        boolean retryPossible = true;
-
-        if (StringUtils.isBlank(task.getSubject()) || task.getRecipients().isEmpty()
-                || StringUtils.isBlank(task.getHtmlBody()) || StringUtils.isBlank(task.getTextBody())) {
-
-            String message = "Could not fetch all required information for sending e-mails:\n"
-                    + task.getRecipients() + "\n"
-                    + task.getSender() + "\n"
-                    + task.getSubject() + "\n"
-                    + task.getHtmlBody() + "\n"
-                    + task.getTextBody();
-            LOG.error(message);
-
-            execution.setStatus(NotificationJob.Status.NOT_SENT.name());
-            retryPossible = false;
-
-            if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
-                execution.setMessage(message);
-            }
-        } else {
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("About to send e-mails:\n"
-                        + task.getRecipients() + "\n"
-                        + task.getSender() + "\n"
-                        + task.getSubject() + "\n"
-                        + task.getHtmlBody() + "\n"
-                        + task.getTextBody() + "\n");
-            }
-
-            for (String to : task.getRecipients()) {
-                try {
-                    MimeMessage message = mailSender.createMimeMessage();
-                    MimeMessageHelper helper = new MimeMessageHelper(message, true);
-                    helper.setTo(to);
-                    helper.setFrom(task.getSender());
-                    helper.setSubject(task.getSubject());
-                    helper.setText(task.getTextBody(), task.getHtmlBody());
-
-                    mailSender.send(message);
-
-                    execution.setStatus(NotificationJob.Status.SENT.name());
-
-                    StringBuilder report = new StringBuilder();
-                    switch (task.getTraceLevel()) {
-                        case ALL:
-                            report.append("FROM: ").append(task.getSender()).append('\n').
-                                    append("TO: ").append(to).append('\n').
-                                    append("SUBJECT: ").append(task.getSubject()).append('\n').append('\n').
-                                    append(task.getTextBody()).append('\n').append('\n').
-                                    append(task.getHtmlBody()).append('\n');
-                            break;
-
-                        case SUMMARY:
-                            report.append("E-mail sent to ").append(to).append('\n');
-                            break;
-
-                        case FAILURES:
-                        case NONE:
-                        default:
-                    }
-                    if (report.length() > 0) {
-                        execution.setMessage(report.toString());
-                    }
-
-                    notificationManager.createTasks(
-                            AuditElements.EventCategoryType.TASK,
-                            "notification",
-                            null,
-                            "send",
-                            AuditElements.Result.SUCCESS,
-                            null,
-                            null,
-                            task,
-                            "Successfully sent notification to " + to);
-                } catch (Exception e) {
-                    LOG.error("Could not send e-mail", e);
-
-                    execution.setStatus(NotificationJob.Status.NOT_SENT.name());
-                    if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
-                        execution.setMessage(ExceptionUtils2.getFullStackTrace(e));
-                    }
-
-                    notificationManager.createTasks(
-                            AuditElements.EventCategoryType.TASK,
-                            "notification",
-                            null,
-                            "send",
-                            AuditElements.Result.FAILURE,
-                            null,
-                            null,
-                            task,
-                            "Could not send notification to " + to, e);
-                }
-
-                execution.setEnd(new Date());
-            }
-        }
-
-        if (hasToBeRegistered(execution)) {
-            execution = notificationManager.storeExec(execution);
-            if (retryPossible
-                    && (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT)) {
-
-                handleRetries(execution);
-            }
-        } else {
-            notificationManager.setTaskExecuted(execution.getTask().getKey(), true);
-        }
-
-        return execution;
-    }
-
-    @Transactional
-    public void execute() throws JobExecutionException {
-        for (NotificationTask task : taskDAO.<NotificationTask>findToExec(TaskType.NOTIFICATION)) {
-            LOG.debug("Found notification task {} to be executed: starting...", task);
-            executeSingle(task);
-            LOG.debug("Notification task {} executed", task);
-        }
-    }
-
-    private boolean hasToBeRegistered(final TaskExec execution) {
-        NotificationTask task = (NotificationTask) execution.getTask();
-
-        // True if either failed and failures have to be registered, or if ALL
-        // has to be registered.
-        return (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT
-                && task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
-                || task.getTraceLevel() == TraceLevel.ALL;
-    }
-
-    private void handleRetries(final TaskExec execution) {
-        if (notificationManager.getMaxRetries() <= 0) {
-            return;
-        }
-
-        long failedExecutionsCount = notificationManager.countExecutionsWithStatus(
-                execution.getTask().getKey(), NotificationJob.Status.NOT_SENT.name());
-
-        if (failedExecutionsCount <= notificationManager.getMaxRetries()) {
-            LOG.debug("Execution of notification task {} will be retried [{}/{}]",
-                    execution.getTask(), failedExecutionsCount, notificationManager.getMaxRetries());
-            notificationManager.setTaskExecuted(execution.getTask().getKey(), false);
-
-            auditManager.audit(
-                    AuditElements.EventCategoryType.TASK,
-                    "notification",
-                    null,
-                    "retry",
-                    AuditElements.Result.SUCCESS,
-                    null,
-                    null,
-                    execution,
-                    "Notification task " + execution.getTask().getKey() + " will be retried");
-        } else {
-            LOG.error("Maximum number of retries reached for task {} - giving up", execution.getTask());
-
-            auditManager.audit(
-                    AuditElements.EventCategoryType.TASK,
-                    "notification",
-                    null,
-                    "retry",
-                    AuditElements.Result.FAILURE,
-                    null,
-                    null,
-                    execution,
-                    "Giving up retries on notification task " + execution.getTask().getKey());
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java
index ede3535..cb52fd0 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.java.job.report;
 
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.core.persistence.api.dao.Reportlet;
 import org.apache.syncope.common.lib.report.ReportletConf;
 import org.slf4j.Logger;
@@ -38,17 +39,18 @@ public abstract class AbstractReportlet implements Reportlet {
         this.conf = conf;
     }
 
-    protected abstract void doExtract(ReportletConf conf, ContentHandler handler) throws SAXException;
+    protected abstract void doExtract(ReportletConf conf, ContentHandler handler, AtomicReference<String> status)
+            throws SAXException;
 
     @Override
     @Transactional(readOnly = true)
-    public void extract(final ContentHandler handler) throws SAXException {
+    public void extract(final ContentHandler handler, final AtomicReference<String> status) throws SAXException {
         AttributesImpl atts = new AttributesImpl();
         atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, conf.getName());
         atts.addAttribute("", "", ReportXMLConst.ATTR_CLASS, ReportXMLConst.XSD_STRING, getClass().getName());
         handler.startElement("", "", ReportXMLConst.ELEMENT_REPORTLET, atts);
 
-        doExtract(conf, handler);
+        doExtract(conf, handler, status);
 
         handler.endElement("", "", ReportXMLConst.ELEMENT_REPORTLET);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
index d6f0ec5..6ba9a53 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.java.job.report;
 
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 import javax.sql.DataSource;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -47,7 +48,9 @@ public class AuditReportlet extends AbstractReportlet {
 
     private DataSource datasource;
 
-    private void doExtractConf(final ContentHandler handler) throws SAXException {
+    private void doExtractConf(final ContentHandler handler, final AtomicReference<String> status) throws SAXException {
+        status.set("Fetching " + conf.getSize() + " rows from the SYNCOPEAUDIT table");
+
         JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource);
         jdbcTemplate.setMaxRows(conf.getSize());
         List<Map<String, Object>> rows = jdbcTemplate.
@@ -120,10 +123,17 @@ public class AuditReportlet extends AbstractReportlet {
             handler.endElement("", "", "event");
         }
         handler.endElement("", "", "events");
+
+        status.set("Fetched " + conf.getSize() + " rows from the SYNCOPEAUDIT table");
     }
 
     @Override
-    protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+    protected void doExtract(
+            final ReportletConf conf,
+            final ContentHandler handler,
+            final AtomicReference<String> status)
+            throws SAXException {
+
         if (conf instanceof AuditReportletConf) {
             this.conf = AuditReportletConf.class.cast(conf);
         } else {
@@ -135,7 +145,7 @@ public class AuditReportlet extends AbstractReportlet {
             throw new ReportException(new IllegalArgumentException("Could not get to DataSource"));
         }
 
-        doExtractConf(handler);
+        doExtractConf(handler, status);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java
new file mode 100644
index 0000000..13d8dde
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java
@@ -0,0 +1,196 @@
+/*
+ * 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.core.provisioning.java.job.report;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.zip.Deflater;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import org.apache.syncope.common.lib.types.ReportExecStatus;
+import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
+import org.apache.syncope.core.persistence.api.dao.ReportDAO;
+import org.apache.syncope.core.persistence.api.dao.ReportExecDAO;
+import org.apache.syncope.core.persistence.api.dao.Reportlet;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.Report;
+import org.apache.syncope.core.persistence.api.entity.ReportExec;
+import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.provisioning.api.job.report.ReportJobDelegate;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.xml.sax.helpers.AttributesImpl;
+
+@Component
+public class DefaultReportJobDelegate implements ReportJobDelegate {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ReportJobDelegate.class);
+
+    /**
+     * Report DAO.
+     */
+    @Autowired
+    private ReportDAO reportDAO;
+
+    /**
+     * Report execution DAO.
+     */
+    @Autowired
+    private ReportExecDAO reportExecDAO;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    private final AtomicReference<String> status = new AtomicReference<>();
+
+    @Override
+    public String currentStatus() {
+        return status.get();
+    }
+
+    @Transactional
+    @Override
+    public void execute(final String reportKey) throws JobExecutionException {
+        Report report = reportDAO.find(reportKey);
+        if (report == null) {
+            throw new JobExecutionException("Report " + reportKey + " not found");
+        }
+
+        if (!report.isActive()) {
+            LOG.info("Report {} not active, aborting...", reportKey);
+            return;
+        }
+
+        // 1. create execution
+        ReportExec execution = entityFactory.newEntity(ReportExec.class);
+        execution.setStatus(ReportExecStatus.STARTED);
+        execution.setStart(new Date());
+        execution.setReport(report);
+        execution = reportExecDAO.save(execution);
+
+        report.add(execution);
+        report = reportDAO.save(report);
+
+        // 2. define a SAX handler for generating result as XML
+        TransformerHandler handler;
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ZipOutputStream zos = new ZipOutputStream(baos);
+        zos.setLevel(Deflater.BEST_COMPRESSION);
+        try {
+            SAXTransformerFactory tFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
+            tFactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
+            handler = tFactory.newTransformerHandler();
+            Transformer serializer = handler.getTransformer();
+            serializer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
+            serializer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+            // a single ZipEntry in the ZipOutputStream
+            zos.putNextEntry(new ZipEntry(report.getName()));
+
+            // streaming SAX handler in a compressed byte array stream
+            handler.setResult(new StreamResult(zos));
+        } catch (Exception e) {
+            throw new JobExecutionException("While configuring for SAX generation", e, true);
+        }
+
+        execution.setStatus(ReportExecStatus.RUNNING);
+        execution = reportExecDAO.save(execution);
+
+        status.set("Starting");
+
+        // 3. actual report execution
+        StringBuilder reportExecutionMessage = new StringBuilder();
+        try {
+            // report header
+            handler.startDocument();
+            AttributesImpl atts = new AttributesImpl();
+            atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, report.getName());
+            handler.startElement("", "", ReportXMLConst.ELEMENT_REPORT, atts);
+
+            status.set("Generating report header");
+
+            // iterate over reportlet instances defined for this report
+            for (Implementation impl : report.getReportlets()) {
+                Optional<Reportlet> reportlet = ImplementationManager.buildReportlet(impl);
+                if (reportlet.isPresent()) {
+                    try {
+                        status.set("Invoking reportlet " + impl.getKey());
+                        reportlet.get().extract(handler, status);
+                    } catch (Throwable t) {
+                        LOG.error("While executing reportlet {} for report {}", reportlet, reportKey, t);
+
+                        execution.setStatus(ReportExecStatus.FAILURE);
+
+                        Throwable effective = t instanceof ReportException
+                                ? t.getCause()
+                                : t;
+                        reportExecutionMessage.
+                                append(ExceptionUtils2.getFullStackTrace(effective)).
+                                append("\n==================\n");
+                    }
+                }
+            }
+
+            // report footer
+            status.set("Generating report footer");
+
+            handler.endElement("", "", ReportXMLConst.ELEMENT_REPORT);
+            handler.endDocument();
+
+            if (!ReportExecStatus.FAILURE.name().equals(execution.getStatus())) {
+                execution.setStatus(ReportExecStatus.SUCCESS);
+            }
+        } catch (Exception e) {
+            execution.setStatus(ReportExecStatus.FAILURE);
+            reportExecutionMessage.append(ExceptionUtils2.getFullStackTrace(e));
+
+            throw new JobExecutionException(e, true);
+        } finally {
+            status.set("Completed");
+
+            try {
+                zos.closeEntry();
+                zos.close();
+                baos.close();
+            } catch (IOException e) {
+                LOG.error("While closing StreamResult's backend", e);
+            }
+
+            execution.setExecResult(baos.toByteArray());
+            execution.setMessage(reportExecutionMessage.toString());
+            execution.setEnd(new Date());
+            reportExecDAO.save(execution);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
index 44a4dfa..b34e271 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.EntityTOUtils;
 import org.apache.syncope.common.lib.SyncopeConstants;
@@ -288,7 +289,12 @@ public class GroupReportlet extends AbstractReportlet {
     }
 
     @Override
-    protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+    protected void doExtract(
+            final ReportletConf conf,
+            final ContentHandler handler,
+            final AtomicReference<String> status)
+            throws SAXException {
+
         if (conf instanceof GroupReportletConf) {
             this.conf = GroupReportletConf.class.cast(conf);
         } else {
@@ -297,7 +303,14 @@ public class GroupReportlet extends AbstractReportlet {
 
         doExtractConf(handler);
 
-        for (int page = 1; page <= (count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+        int total = count();
+        int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+        status.set("Processing " + total + " groups in " + pages + " pages");
+
+        for (int page = 1; page <= pages; page++) {
+            status.set("Processing " + total + " groups: page " + page + " of " + pages);
+
             List<Group> groups;
             if (StringUtils.isBlank(this.conf.getMatchingCond())) {
                 groups = groupDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE);
@@ -312,6 +325,8 @@ public class GroupReportlet extends AbstractReportlet {
             }
 
             doExtract(handler, groups);
+
+            status.set("Processed " + total + " groups: page " + page + " of " + pages);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
index 25da1f4..da358fd 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeConstants;
@@ -49,7 +50,6 @@ import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
-import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
@@ -366,25 +366,13 @@ public class ReconciliationReportlet extends AbstractReportlet {
         }
     }
 
-    private void doExtract(
-            final ContentHandler handler, final int count, final SearchCond cond, final AnyTypeKind anyTypeKind)
+    @Override
+    protected void doExtract(
+            final ReportletConf conf,
+            final ContentHandler handler,
+            final AtomicReference<String> status)
             throws SAXException {
 
-        for (int page = 1; page <= (count / PAGE_SIZE) + 1; page++) {
-            List<AnyObject> anys = searchDAO.search(
-                    SyncopeConstants.FULL_ADMIN_REALMS,
-                    cond,
-                    page,
-                    PAGE_SIZE,
-                    Collections.<OrderByClause>emptyList(),
-                    anyTypeKind);
-
-            doExtract(handler, anys);
-        }
-    }
-
-    @Override
-    protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
         if (conf instanceof ReconciliationReportletConf) {
             this.conf = ReconciliationReportletConf.class.cast(conf);
         } else {
@@ -394,39 +382,81 @@ public class ReconciliationReportlet extends AbstractReportlet {
         AttributesImpl atts = new AttributesImpl();
 
         if (StringUtils.isBlank(this.conf.getUserMatchingCond())) {
-            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(userDAO.count()));
+            int total = userDAO.count();
+            int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+            status.set("Processing " + total + " users in " + pages + " pages");
+
+            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(total));
             handler.startElement("", "", getAnyElementName(AnyTypeKind.USER) + "s", atts);
 
-            for (int page = 1; page <= (userDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+            for (int page = 1; page <= pages; page++) {
+                status.set("Processing " + total + " users: page " + page + " of " + pages);
+
                 doExtract(handler, userDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE));
             }
         } else {
             SearchCond cond = SearchCondConverter.convert(this.conf.getUserMatchingCond());
 
-            int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.USER);
-            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(count));
+            int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.USER);
+            int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+            status.set("Processing " + total + " users in " + pages + " pages");
+
+            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(total));
             handler.startElement("", "", getAnyElementName(AnyTypeKind.USER) + "s", atts);
 
-            doExtract(handler, count, cond, AnyTypeKind.USER);
+            for (int page = 1; page <= pages; page++) {
+                status.set("Processing " + total + " users: page " + page + " of " + pages);
+
+                doExtract(handler, searchDAO.search(
+                        SyncopeConstants.FULL_ADMIN_REALMS,
+                        cond,
+                        page,
+                        PAGE_SIZE,
+                        Collections.<OrderByClause>emptyList(),
+                        AnyTypeKind.USER));
+            }
         }
         handler.endElement("", "", getAnyElementName(AnyTypeKind.USER) + "s");
 
         atts.clear();
         if (StringUtils.isBlank(this.conf.getGroupMatchingCond())) {
-            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(groupDAO.count()));
+            int total = groupDAO.count();
+            int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+            status.set("Processing " + total + " groups in " + pages + " pages");
+
+            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(total));
             handler.startElement("", "", getAnyElementName(AnyTypeKind.GROUP) + "s", atts);
 
-            for (int page = 1; page <= (groupDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+            for (int page = 1; page <= pages; page++) {
+                status.set("Processing " + total + " groups: page " + page + " of " + pages);
+
                 doExtract(handler, groupDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE));
             }
         } else {
             SearchCond cond = SearchCondConverter.convert(this.conf.getUserMatchingCond());
 
-            int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.GROUP);
-            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(count));
+            int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.GROUP);
+            int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+            status.set("Processing " + total + " groups in " + pages + " pages");
+
+            atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(total));
             handler.startElement("", "", getAnyElementName(AnyTypeKind.GROUP) + "s", atts);
 
-            doExtract(handler, count, cond, AnyTypeKind.GROUP);
+            for (int page = 1; page <= pages; page++) {
+                status.set("Processing " + total + " groups: page " + page + " of " + pages);
+
+                doExtract(handler, searchDAO.search(
+                        SyncopeConstants.FULL_ADMIN_REALMS,
+                        cond,
+                        page,
+                        PAGE_SIZE,
+                        Collections.<OrderByClause>emptyList(),
+                        AnyTypeKind.GROUP));
+            }
         }
         handler.endElement("", "", getAnyElementName(AnyTypeKind.GROUP) + "s");
 
@@ -440,14 +470,28 @@ public class ReconciliationReportlet extends AbstractReportlet {
                                 SearchCond.getLeafCond(anyTypeCond),
                                 SearchCondConverter.convert(this.conf.getAnyObjectMatchingCond()));
 
-                int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.ANY_OBJECT);
+                int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.ANY_OBJECT);
+                int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+                status.set("Processing " + total + " any objects " + anyType.getKey() + " in " + pages + " pages");
 
                 atts.clear();
                 atts.addAttribute("", "", "type", ReportXMLConst.XSD_STRING, anyType.getKey());
-                atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(count));
+                atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(total));
                 handler.startElement("", "", getAnyElementName(AnyTypeKind.ANY_OBJECT) + "s", atts);
 
-                doExtract(handler, count, cond, AnyTypeKind.ANY_OBJECT);
+                for (int page = 1; page <= pages; page++) {
+                    status.set("Processing " + total + " any objects " + anyType.getKey()
+                            + ": page " + page + " of " + pages);
+
+                    doExtract(handler, searchDAO.search(
+                            SyncopeConstants.FULL_ADMIN_REALMS,
+                            cond,
+                            page,
+                            PAGE_SIZE,
+                            Collections.<OrderByClause>emptyList(),
+                            AnyTypeKind.ANY_OBJECT));
+                }
 
                 handler.endElement("", "", getAnyElementName(AnyTypeKind.ANY_OBJECT) + "s");
             }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java
index b05c507..be42b1b 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java
@@ -18,12 +18,14 @@
  */
 package org.apache.syncope.core.provisioning.java.job.report;
 
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.provisioning.java.job.AbstractInterruptableJob;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.apache.syncope.core.provisioning.api.job.JobManager;
+import org.apache.syncope.core.provisioning.api.job.report.ReportJobDelegate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,6 +54,11 @@ public class ReportJob extends AbstractInterruptableJob {
     }
 
     @Override
+    public JobDelegate getDelegate() {
+        return delegate;
+    }
+
+    @Override
     public void execute(final JobExecutionContext context) throws JobExecutionException {
         super.execute(context);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java
deleted file mode 100644
index 7a9cfc7..0000000
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java
+++ /dev/null
@@ -1,177 +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.core.provisioning.java.job.report;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Date;
-import java.util.Optional;
-import java.util.zip.Deflater;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.sax.SAXTransformerFactory;
-import javax.xml.transform.sax.TransformerHandler;
-import javax.xml.transform.stream.StreamResult;
-import org.apache.syncope.common.lib.types.ReportExecStatus;
-import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
-import org.apache.syncope.core.persistence.api.dao.ReportDAO;
-import org.apache.syncope.core.persistence.api.dao.ReportExecDAO;
-import org.apache.syncope.core.persistence.api.dao.Reportlet;
-import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.api.entity.Implementation;
-import org.apache.syncope.core.persistence.api.entity.Report;
-import org.apache.syncope.core.persistence.api.entity.ReportExec;
-import org.apache.syncope.core.spring.ImplementationManager;
-import org.quartz.JobExecutionException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
-import org.xml.sax.helpers.AttributesImpl;
-
-@Component
-public class ReportJobDelegate {
-
-    private static final Logger LOG = LoggerFactory.getLogger(ReportJobDelegate.class);
-
-    /**
-     * Report DAO.
-     */
-    @Autowired
-    private ReportDAO reportDAO;
-
-    /**
-     * Report execution DAO.
-     */
-    @Autowired
-    private ReportExecDAO reportExecDAO;
-
-    @Autowired
-    private EntityFactory entityFactory;
-
-    @Transactional
-    public void execute(final String reportKey) throws JobExecutionException {
-        Report report = reportDAO.find(reportKey);
-        if (report == null) {
-            throw new JobExecutionException("Report " + reportKey + " not found");
-        }
-
-        if (!report.isActive()) {
-            LOG.info("Report {} not active, aborting...", reportKey);
-            return;
-        }
-
-        // 1. create execution
-        ReportExec execution = entityFactory.newEntity(ReportExec.class);
-        execution.setStatus(ReportExecStatus.STARTED);
-        execution.setStart(new Date());
-        execution.setReport(report);
-        execution = reportExecDAO.save(execution);
-
-        report.add(execution);
-        report = reportDAO.save(report);
-
-        // 2. define a SAX handler for generating result as XML
-        TransformerHandler handler;
-
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        ZipOutputStream zos = new ZipOutputStream(baos);
-        zos.setLevel(Deflater.BEST_COMPRESSION);
-        try {
-            SAXTransformerFactory tFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
-            tFactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
-            handler = tFactory.newTransformerHandler();
-            Transformer serializer = handler.getTransformer();
-            serializer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
-            serializer.setOutputProperty(OutputKeys.INDENT, "yes");
-
-            // a single ZipEntry in the ZipOutputStream
-            zos.putNextEntry(new ZipEntry(report.getName()));
-
-            // streaming SAX handler in a compressed byte array stream
-            handler.setResult(new StreamResult(zos));
-        } catch (Exception e) {
-            throw new JobExecutionException("While configuring for SAX generation", e, true);
-        }
-
-        execution.setStatus(ReportExecStatus.RUNNING);
-        execution = reportExecDAO.save(execution);
-
-        // 3. actual report execution
-        StringBuilder reportExecutionMessage = new StringBuilder();
-        try {
-            // report header
-            handler.startDocument();
-            AttributesImpl atts = new AttributesImpl();
-            atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, report.getName());
-            handler.startElement("", "", ReportXMLConst.ELEMENT_REPORT, atts);
-
-            // iterate over reportlet instances defined for this report
-            for (Implementation impl : report.getReportlets()) {
-                Optional<Reportlet> reportlet = ImplementationManager.buildReportlet(impl);
-                if (reportlet.isPresent()) {
-                    try {
-                        reportlet.get().extract(handler);
-                    } catch (Throwable t) {
-                        LOG.error("While executing reportlet {} for report {}", reportlet, reportKey, t);
-
-                        execution.setStatus(ReportExecStatus.FAILURE);
-
-                        Throwable effective = t instanceof ReportException
-                                ? t.getCause()
-                                : t;
-                        reportExecutionMessage.
-                                append(ExceptionUtils2.getFullStackTrace(effective)).
-                                append("\n==================\n");
-                    }
-                }
-            }
-
-            // report footer
-            handler.endElement("", "", ReportXMLConst.ELEMENT_REPORT);
-            handler.endDocument();
-
-            if (!ReportExecStatus.FAILURE.name().equals(execution.getStatus())) {
-                execution.setStatus(ReportExecStatus.SUCCESS);
-            }
-        } catch (Exception e) {
-            execution.setStatus(ReportExecStatus.FAILURE);
-            reportExecutionMessage.append(ExceptionUtils2.getFullStackTrace(e));
-
-            throw new JobExecutionException(e, true);
-        } finally {
-            try {
-                zos.closeEntry();
-                zos.close();
-                baos.close();
-            } catch (IOException e) {
-                LOG.error("While closing StreamResult's backend", e);
-            }
-
-            execution.setExecResult(baos.toByteArray());
-            execution.setMessage(reportExecutionMessage.toString());
-            execution.setEnd(new Date());
-            reportExecDAO.save(execution);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java
index 1156d25..49e5e2a 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.java.job.report;
 
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.core.persistence.api.dao.ReportletConfClass;
 import org.apache.syncope.common.lib.report.ReportletConf;
 import org.apache.syncope.common.lib.report.StaticReportletConf;
@@ -70,7 +71,12 @@ public class StaticReportlet extends AbstractReportlet {
     }
 
     @Override
-    protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+    protected void doExtract(
+            final ReportletConf conf,
+            final ContentHandler handler,
+            final AtomicReference<String> status)
+            throws SAXException {
+
         if (conf instanceof StaticReportletConf) {
             this.conf = StaticReportletConf.class.cast(conf);
         } else {

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
index 3f9384b..8d51e4a 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.EntityTOUtils;
 import org.apache.syncope.common.lib.SyncopeConstants;
@@ -355,7 +356,12 @@ public class UserReportlet extends AbstractReportlet {
     }
 
     @Override
-    protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+    protected void doExtract(
+            final ReportletConf conf,
+            final ContentHandler handler,
+            final AtomicReference<String> status)
+            throws SAXException {
+
         if (conf instanceof UserReportletConf) {
             this.conf = UserReportletConf.class.cast(conf);
         } else {
@@ -364,7 +370,14 @@ public class UserReportlet extends AbstractReportlet {
 
         doExtractConf(handler);
 
-        for (int page = 1; page <= (count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+        int total = count();
+        int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+        status.set("Processing " + total + " users in " + pages + " pages");
+
+        for (int page = 1; page <= pages; page++) {
+            status.set("Processing " + total + " users: page " + page + " of " + pages);
+
             List<User> users;
             if (StringUtils.isBlank(this.conf.getMatchingCond())) {
                 users = userDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE);

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index b22d175..3e12970 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.collections.IteratorChain;
 import org.apache.syncope.common.lib.to.ExecTO;
@@ -179,7 +180,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             final PropagationTask task,
             final ConnectorObject beforeObj,
             final Connector connector,
-            final Boolean[] propagationAttempted) {
+            final AtomicReference<Boolean> propagationAttempted) {
 
         // set of attributes to be propagated
         Set<Attribute> attributes = new HashSet<>(task.getAttributes());
@@ -270,7 +271,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             final PropagationTask task,
             final ConnectorObject beforeObj,
             final Connector connector,
-            final Boolean[] propagationAttempted) {
+            final AtomicReference<Boolean> propagationAttempted) {
 
         Uid result;
         if (beforeObj == null) {
@@ -363,7 +364,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         String failureReason = null;
 
         // Flag to state whether any propagation has been attempted
-        Boolean[] propagationAttempted = new Boolean[] { false };
+        AtomicReference<Boolean> propagationAttempted = new AtomicReference<>(false);
 
         ConnectorObject beforeObj = null;
         ConnectorObject afterObj = null;
@@ -402,7 +403,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                 default:
             }
 
-            execution.setStatus(propagationAttempted[0]
+            execution.setStatus(propagationAttempted.get()
                     ? PropagationTaskExecStatus.SUCCESS.name()
                     : PropagationTaskExecStatus.NOT_ATTEMPTED.name());
 
@@ -434,7 +435,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                 LOG.error("While executing KO action on {}", execution, wft);
             }
 
-            propagationAttempted[0] = true;
+            propagationAttempted.set(true);
 
             actions.forEach(action -> {
                 action.onError(task, execution, e);

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
index 0a74cb3..38a7145 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
@@ -130,6 +130,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
             }
 
             doHandle(delta, provision);
+            executor.reportHandled(delta.getObjectClass(), delta.getObject().getName());
 
             LOG.debug("Successfully handled {}", delta);
 
@@ -159,6 +160,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
             LOG.warn("Ignoring during pull", e);
 
             executor.setLatestSyncToken(delta.getObjectClass(), delta.getToken());
+            executor.reportHandled(delta.getObjectClass(), delta.getObject().getName());
 
             return true;
         } catch (JobExecutionException e) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
index c274dc6..6ff95fc 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
@@ -89,6 +89,7 @@ public class DefaultRealmPullResultHandler
             }
 
             doHandle(delta, orgUnit);
+            executor.reportHandled(delta.getObjectClass(), delta.getObject().getName());
 
             LOG.debug("Successfully handled {}", delta);
 
@@ -117,6 +118,7 @@ public class DefaultRealmPullResultHandler
             LOG.warn("Ignoring during pull", e);
 
             executor.setLatestSyncToken(delta.getObjectClass(), delta.getToken());
+            executor.reportHandled(delta.getObjectClass(), delta.getObject().getName());
 
             return true;
         } catch (JobExecutionException e) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
index 63e02de..e1fd52c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
@@ -29,6 +29,7 @@ import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.collections.IteratorChain;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
+import org.apache.commons.lang3.tuple.MutablePair;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
@@ -52,6 +53,7 @@ import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullExecutor;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.UserPullResultHandler;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+import org.identityconnectors.framework.common.objects.Name;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.identityconnectors.framework.common.objects.OperationOptions;
 import org.identityconnectors.framework.common.objects.SyncToken;
@@ -74,6 +76,8 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
 
     protected final Map<ObjectClass, SyncToken> latestSyncTokens = new HashMap<>();
 
+    protected final Map<ObjectClass, MutablePair<Integer, String>> handled = new HashMap<>();
+
     protected ProvisioningProfile<PullTask, PullActions> profile;
 
     protected RealmPullResultHandler rhandler;
@@ -89,6 +93,34 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
         latestSyncTokens.put(objectClass, latestSyncToken);
     }
 
+    @Override
+    public void reportHandled(final ObjectClass objectClass, final Name name) {
+        MutablePair<Integer, String> pair = handled.get(objectClass);
+        if (pair == null) {
+            pair = MutablePair.of(0, null);
+            handled.put(objectClass, pair);
+        }
+        pair.setLeft(pair.getLeft() + 1);
+        pair.setRight(name.getNameValue());
+    }
+
+    @Override
+    public String currentStatus() {
+        synchronized (status) {
+            if (!handled.isEmpty()) {
+                StringBuilder builder = new StringBuilder("Processed:\n");
+                handled.forEach((key, value) -> {
+                    builder.append(' ').append(value.getLeft()).append('\t').
+                            append(key.getObjectClassValue()).
+                            append("\t/ latest: ").append(value.getRight()).
+                            append('\n');
+                });
+                status.set(builder.toString());
+            }
+        }
+        return status.get();
+    }
+
     protected void setGroupOwners(final GroupPullResultHandler ghandler) {
         ghandler.getGroupOwnerMap().entrySet().stream().map(entry -> {
             Group group = groupDAO.find(entry.getKey());
@@ -193,8 +225,12 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
             }
         }
 
+        status.set("Initialization completed");
+
         // First realms...
         if (pullTask.getResource().getOrgUnit() != null) {
+            status.set("Pulling " + pullTask.getResource().getOrgUnit().getObjectClass().getObjectClassValue());
+
             OrgUnit orgUnit = pullTask.getResource().getOrgUnit();
             OperationOptions options = MappingUtils.buildOperationOptions(
                     MappingUtils.getPullItems(orgUnit.getItems()).iterator());
@@ -248,6 +284,8 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
 
         for (Provision provision : pullTask.getResource().getProvisions()) {
             if (provision.getMapping() != null) {
+                status.set("Pulling " + provision.getObjectClass().getObjectClassValue());
+
                 SyncopePullResultHandler handler;
                 switch (provision.getAnyType().getKind()) {
                     case USER:
@@ -322,6 +360,8 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
             }
         }
 
+        status.set("Pull done");
+
         String result = createReport(profile.getResults(), pullTask.getResource(), dryRun);
         LOG.debug("Pull result: {}", result);
         return result;

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
index b1a8b07..1967487 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
@@ -20,9 +20,13 @@ package org.apache.syncope.core.provisioning.java.pushpull;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.MutablePair;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
@@ -36,10 +40,13 @@ import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
+import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 import org.apache.syncope.core.persistence.api.entity.task.PushTaskAnyFilter;
+import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.pushpull.AnyObjectPushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.GroupPushResultHandler;
@@ -81,6 +88,8 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
 
     protected ProvisioningProfile<PushTask, PushActions> profile;
 
+    protected final Map<String, MutablePair<Integer, String>> handled = new HashMap<>();
+
     protected RealmPushResultHandler rhandler;
 
     protected AnyObjectPushResultHandler ahandler;
@@ -89,6 +98,33 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
 
     protected GroupPushResultHandler ghandler;
 
+    protected void reportHandled(final String anyType, final String key) {
+        MutablePair<Integer, String> pair = handled.get(anyType);
+        if (pair == null) {
+            pair = MutablePair.of(0, null);
+            handled.put(anyType, pair);
+        }
+        pair.setLeft(pair.getLeft() + 1);
+        pair.setRight(key);
+    }
+
+    @Override
+    public String currentStatus() {
+        synchronized (status) {
+            if (!handled.isEmpty()) {
+                StringBuilder builder = new StringBuilder("Processed:\n");
+                handled.forEach((key, value) -> {
+                    builder.append(' ').append(value.getLeft()).append('\t').
+                            append(key).
+                            append("\t/ latest: ").append(value.getRight()).
+                            append('\n');
+                });
+                status.set(builder.toString());
+            }
+        }
+        return status.get();
+    }
+
     protected AnyDAO<?> getAnyDAO(final AnyTypeKind anyTypeKind) {
         AnyDAO<?> result;
         switch (anyTypeKind) {
@@ -117,6 +153,13 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         for (Any<?> any : anys) {
             try {
                 handler.handle(any.getKey());
+                reportHandled(
+                        any.getType().getKey(),
+                        (any instanceof User
+                                ? ((User) any).getUsername()
+                                : any instanceof Group
+                                        ? ((Group) any).getName()
+                                        : ((AnyObject) any).getName()));
             } catch (Exception e) {
                 LOG.warn("Failure pushing '{}' on '{}'", any, resource, e);
                 throw new JobExecutionException("While pushing " + any + " on " + resource, e);
@@ -184,8 +227,12 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
             }
         }
 
+        status.set("Initialization completed");
+
         // First realms...
         if (pushTask.getResource().getOrgUnit() != null) {
+            status.set("Pushing realms");
+
             rhandler = buildRealmHandler();
 
             for (Realm realm : realmDAO.findDescendants(profile.getTask().getSourceRealm())) {
@@ -193,6 +240,7 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
                 if (realm.getParent() != null) {
                     try {
                         rhandler.handle(realm.getKey());
+                        reportHandled(SyncopeConstants.REALM_ANYTYPE, realm.getName());
                     } catch (Exception e) {
                         LOG.warn("Failure pushing '{}' on '{}'", realm, pushTask.getResource(), e);
                         throw new JobExecutionException("While pushing " + realm + " on " + pushTask.getResource(), e);
@@ -208,6 +256,8 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
 
         for (Provision provision : pushTask.getResource().getProvisions()) {
             if (provision.getMapping() != null) {
+                status.set("Pushing " + provision.getAnyType().getKey());
+
                 AnyDAO<?> anyDAO = getAnyDAO(provision.getAnyType().getKind());
 
                 SyncopePushResultHandler handler;
@@ -255,6 +305,8 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
             }
         }
 
+        status.set("Push done");
+
         String result = createReport(profile.getResults(), pushTask.getResource(), dryRun);
         LOG.debug("Push result: {}", result);
         return result;

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractExecutableService.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractExecutableService.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractExecutableService.java
index aadbaf3..81ef47c 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractExecutableService.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractExecutableService.java
@@ -71,6 +71,11 @@ public abstract class AbstractExecutableService extends AbstractServiceImpl impl
     }
 
     @Override
+    public JobTO getJob(final String key) {
+        return getExecutableLogic().getJob(key);
+    }
+
+    @Override
     public List<JobTO> listJobs() {
         return getExecutableLogic().listJobs();
     }


[02/11] syncope git commit: Adding pagination support to sample groovy script for usage with Scripted REST connector

Posted by il...@apache.org.
Adding pagination support to sample groovy script for usage with Scripted REST connector


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/990a4651
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/990a4651
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/990a4651

Branch: refs/heads/2_0_X
Commit: 990a4651596a30eab683e5051149b600e5973439
Parents: 3e2ebb9
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Thu Mar 1 17:21:15 2018 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Thu Mar 1 17:21:15 2018 +0100

----------------------------------------------------------------------
 .../src/test/resources/rest/SearchScript.groovy | 40 ++++++++++++++++++--
 1 file changed, 37 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/990a4651/fit/core-reference/src/test/resources/rest/SearchScript.groovy
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/resources/rest/SearchScript.groovy b/fit/core-reference/src/test/resources/rest/SearchScript.groovy
index 118da8a..ba7a2e8 100644
--- a/fit/core-reference/src/test/resources/rest/SearchScript.groovy
+++ b/fit/core-reference/src/test/resources/rest/SearchScript.groovy
@@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode
 import javax.ws.rs.core.Response
 import org.apache.cxf.jaxrs.client.WebClient
 import org.identityconnectors.common.security.GuardedString
+import org.identityconnectors.framework.common.objects.OperationOptions;
 
 // Parameters:
 // The connector sends the following:
@@ -66,6 +67,24 @@ def buildConnectorObject(node) {
 
 log.info("Entering " + action + " Script");
 
+// ----------------
+// Manage pagination
+// ----------------
+def offset = options[OperationOptions.OP_PAGED_RESULTS_COOKIE] == null
+? 0
+: options[OperationOptions.OP_PAGED_RESULTS_COOKIE].toInteger();
+ 
+def pageSize = options[OperationOptions.OP_PAGE_SIZE] == null 
+? 100
+: options[OperationOptions.OP_PAGE_SIZE].toInteger();
+
+def limit = offset + pageSize;
+
+log.ok("pagedResultsCookie: " + offset);
+log.ok("pageSize: " + pageSize);
+log.ok("limit: " + limit);
+// ----------------
+
 WebClient webClient = client;
 ObjectMapper mapper = new ObjectMapper();
 
@@ -76,10 +95,11 @@ case "__ACCOUNT__":
   if (query == null || (!query.get("left").equals("__UID__") && !query.get("conditionType").equals("EQUALS"))) {
     webClient.path("/users");
     Response response = webClient.get();    
-    ArrayNode node = mapper.readTree(response.getEntity());
+    ArrayNode nodes = mapper.readTree(response.getEntity());
     
-    for (i = 0; i < node.size(); i++) {
-      result.add(buildConnectorObject(node.get(i)));
+    // beware: this is not enforcing any server-side pagination feature
+    for (i = offset; i < (limit < nodes.size() ? limit: nodes.size()); i++) {
+      result.add(buildConnectorObject(nodes.get(i)));
     }
   } else {
     webClient.path("/users/" + query.get("right"));
@@ -98,4 +118,18 @@ default:
   result;
 }
 
+// ----------------
+// Return paged result cookie
+// ----------------
+def pagedResultCookieLine = [:]
+if (pageSize > result.size()) {
+  // no more results
+  pagedResultCookieLine.put(OperationOptions.OP_PAGED_RESULTS_COOKIE, null);
+} else {
+  pagedResultCookieLine.put(OperationOptions.OP_PAGED_RESULTS_COOKIE, "" + limit);
+}
+ 
+result.add(pagedResultCookieLine);
+// ----------------
+
 return result;


[04/11] syncope git commit: Upgrading groovy

Posted by il...@apache.org.
Upgrading groovy


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/dffa0869
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/dffa0869
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/dffa0869

Branch: refs/heads/master
Commit: dffa08696cf244eb83210c2a5ac9405ed94b2f09
Parents: 2d2702a
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Fri Mar 2 09:09:55 2018 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Mar 2 09:09:55 2018 +0100

----------------------------------------------------------------------
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/dffa0869/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 0844233..522db78 100644
--- a/pom.xml
+++ b/pom.xml
@@ -382,7 +382,7 @@ under the License.
 
     <cocoon.version>3.0.0-alpha-3</cocoon.version>
 
-    <groovy.version>2.5.0-beta-2</groovy.version>
+    <groovy.version>2.5.0-beta-3</groovy.version>
 
     <flowable.version>6.2.1</flowable.version>
 


[09/11] syncope git commit: Adding pagination support to sample groovy script for usage with Scripted REST connector

Posted by il...@apache.org.
Adding pagination support to sample groovy script for usage with Scripted REST connector


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/4f6fa1a2
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/4f6fa1a2
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/4f6fa1a2

Branch: refs/heads/master
Commit: 4f6fa1a252f399b225df4c7a73c66201b1ffbd51
Parents: e073cc4
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Thu Mar 1 17:21:15 2018 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Mar 2 12:13:41 2018 +0100

----------------------------------------------------------------------
 .../src/test/resources/rest/SearchScript.groovy | 40 ++++++++++++++++++--
 1 file changed, 37 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/4f6fa1a2/fit/core-reference/src/test/resources/rest/SearchScript.groovy
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/resources/rest/SearchScript.groovy b/fit/core-reference/src/test/resources/rest/SearchScript.groovy
index 118da8a..ba7a2e8 100644
--- a/fit/core-reference/src/test/resources/rest/SearchScript.groovy
+++ b/fit/core-reference/src/test/resources/rest/SearchScript.groovy
@@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode
 import javax.ws.rs.core.Response
 import org.apache.cxf.jaxrs.client.WebClient
 import org.identityconnectors.common.security.GuardedString
+import org.identityconnectors.framework.common.objects.OperationOptions;
 
 // Parameters:
 // The connector sends the following:
@@ -66,6 +67,24 @@ def buildConnectorObject(node) {
 
 log.info("Entering " + action + " Script");
 
+// ----------------
+// Manage pagination
+// ----------------
+def offset = options[OperationOptions.OP_PAGED_RESULTS_COOKIE] == null
+? 0
+: options[OperationOptions.OP_PAGED_RESULTS_COOKIE].toInteger();
+ 
+def pageSize = options[OperationOptions.OP_PAGE_SIZE] == null 
+? 100
+: options[OperationOptions.OP_PAGE_SIZE].toInteger();
+
+def limit = offset + pageSize;
+
+log.ok("pagedResultsCookie: " + offset);
+log.ok("pageSize: " + pageSize);
+log.ok("limit: " + limit);
+// ----------------
+
 WebClient webClient = client;
 ObjectMapper mapper = new ObjectMapper();
 
@@ -76,10 +95,11 @@ case "__ACCOUNT__":
   if (query == null || (!query.get("left").equals("__UID__") && !query.get("conditionType").equals("EQUALS"))) {
     webClient.path("/users");
     Response response = webClient.get();    
-    ArrayNode node = mapper.readTree(response.getEntity());
+    ArrayNode nodes = mapper.readTree(response.getEntity());
     
-    for (i = 0; i < node.size(); i++) {
-      result.add(buildConnectorObject(node.get(i)));
+    // beware: this is not enforcing any server-side pagination feature
+    for (i = offset; i < (limit < nodes.size() ? limit: nodes.size()); i++) {
+      result.add(buildConnectorObject(nodes.get(i)));
     }
   } else {
     webClient.path("/users/" + query.get("right"));
@@ -98,4 +118,18 @@ default:
   result;
 }
 
+// ----------------
+// Return paged result cookie
+// ----------------
+def pagedResultCookieLine = [:]
+if (pageSize > result.size()) {
+  // no more results
+  pagedResultCookieLine.put(OperationOptions.OP_PAGED_RESULTS_COOKIE, null);
+} else {
+  pagedResultCookieLine.put(OperationOptions.OP_PAGED_RESULTS_COOKIE, "" + limit);
+}
+ 
+result.add(pagedResultCookieLine);
+// ----------------
+
 return result;


[11/11] syncope git commit: [SYNCOPE-1279] Now providing runtime status updates from running Tasks and Reports

Posted by il...@apache.org.
[SYNCOPE-1279] Now providing runtime status updates from running Tasks and Reports


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/772206a4
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/772206a4
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/772206a4

Branch: refs/heads/master
Commit: 772206a4cb0cadb5ace021e3608d5fcd5b78f505
Parents: 4f6fa1a
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Fri Mar 2 11:45:34 2018 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Mar 2 12:47:00 2018 +0100

----------------------------------------------------------------------
 .../console/reports/ReportDirectoryPanel.java   |  56 ++++
 .../console/rest/AnyObjectRestClient.java       |   2 -
 .../client/console/rest/ReportRestClient.java   |   4 +
 .../client/console/rest/TaskRestClient.java     |   4 +
 .../tasks/ProvisioningTaskDirectoryPanel.java   |  61 ++++
 .../client/console/widgets/JobActionPanel.java  |  23 +-
 .../client/console/widgets/JobWidget.java       |   4 +-
 .../META-INF/resources/css/syncopeConsole.css   |   6 +-
 .../client/console/widgets/JobActionPanel.html  |   2 +
 .../org/apache/syncope/common/lib/to/JobTO.java |  11 +
 .../rest/api/service/ExecutableService.java     |  11 +
 .../core/logic/AbstractExecutableLogic.java     |   2 +
 .../syncope/core/logic/AbstractJobLogic.java    |  63 ++--
 .../apache/syncope/core/logic/ReportLogic.java  |  25 ++
 .../apache/syncope/core/logic/TaskLogic.java    |  27 +-
 .../core/persistence/api/dao/Reportlet.java     |   4 +-
 .../core/provisioning/api/Connector.java        |   7 +-
 .../core/provisioning/api/job/JobDelegate.java  |  27 ++
 .../api/job/SchedTaskJobDelegate.java           |   2 +-
 .../api/job/report/ReportJobDelegate.java       |  27 ++
 .../notification/NotificationJobDelegate.java   |  31 ++
 .../api/pushpull/SyncopePullExecutor.java       |   3 +
 .../provisioning/java/ConnectorFacadeProxy.java |  13 +-
 .../java/job/AbstractInterruptableJob.java      |  21 +-
 .../java/job/AbstractSchedTaskJobDelegate.java  |  12 +
 .../GroupMemberProvisionTaskJobDelegate.java    |  28 +-
 .../java/job/IdentityRecertification.java       |   9 +-
 .../core/provisioning/java/job/TaskJob.java     |  21 +-
 .../DefaultNotificationJobDelegate.java         | 296 +++++++++++++++++++
 .../java/job/notification/NotificationJob.java  |   7 +
 .../notification/NotificationJobDelegate.java   | 278 -----------------
 .../java/job/report/AbstractReportlet.java      |   8 +-
 .../java/job/report/AuditReportlet.java         |  16 +-
 .../job/report/DefaultReportJobDelegate.java    | 196 ++++++++++++
 .../java/job/report/GroupReportlet.java         |  19 +-
 .../job/report/ReconciliationReportlet.java     | 106 +++++--
 .../provisioning/java/job/report/ReportJob.java |   7 +
 .../java/job/report/ReportJobDelegate.java      | 177 -----------
 .../java/job/report/StaticReportlet.java        |   8 +-
 .../java/job/report/UserReportlet.java          |  17 +-
 .../AbstractPropagationTaskExecutor.java        |  11 +-
 .../pushpull/AbstractPullResultHandler.java     |   2 +
 .../pushpull/DefaultRealmPullResultHandler.java |   2 +
 .../java/pushpull/PullJobDelegate.java          |  40 +++
 .../java/pushpull/PushJobDelegate.java          |  52 ++++
 .../cxf/service/AbstractExecutableService.java  |   5 +
 46 files changed, 1191 insertions(+), 562 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
index 4d1c1c2..21b1563 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
@@ -34,27 +34,36 @@ import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.panels.DirectoryPanel;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.rest.ReportRestClient;
+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;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.console.widgets.JobActionPanel;
 import org.apache.syncope.client.console.wizards.AjaxWizard;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.to.ReportTO;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
 import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
 import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.model.CompoundPropertyModel;
 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;
 
 /**
  * Reports page.
@@ -76,6 +85,17 @@ public abstract class ReportDirectoryPanel
         modal.size(Modal.Size.Large);
         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 ReportStartAtTogglePanel(container, pageRef);
         addInnerObject(startAt);
     }
@@ -105,10 +125,46 @@ public abstract class ReportDirectoryPanel
         columns.add(new BooleanPropertyColumn<>(
                 new StringResourceModel("active", this), "active", "active"));
 
+        columns.add(new AbstractColumn<ReportTO, String>(new Model<>(""), "running") {
+
+            private static final long serialVersionUID = 4209532514416998046L;
+
+            @Override
+            public void populateItem(
+                    final Item<ICellPopulator<ReportTO>> cellItem,
+                    final String componentId,
+                    final IModel<ReportTO> rowModel) {
+
+                JobTO jobTO = restClient.getJob(rowModel.getObject().getKey());
+                JobActionPanel panel = new JobActionPanel(
+                        componentId, jobTO, false, ReportDirectoryPanel.this, pageRef);
+                MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE,
+                        String.format("%s,%s",
+                                StandardEntitlement.TASK_EXECUTE,
+                                StandardEntitlement.TASK_UPDATE));
+                cellItem.add(panel);
+            }
+
+            @Override
+            public String getCssClass() {
+                return "col-xs-1";
+            }
+        });
+
         return columns;
     }
 
     @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof JobActionPanel.JobActionPayload) {
+            container.modelChanged();
+            JobActionPanel.JobActionPayload.class.cast(event.getPayload()).getTarget().add(container);
+        } else {
+            super.onEvent(event);
+        }
+    }
+
+    @Override
     public ActionsPanel<ReportTO> getActions(final IModel<ReportTO> model) {
         final ActionsPanel<ReportTO> panel = super.getActions(model);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java
index 31cd3a8..a885764 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java
@@ -18,8 +18,6 @@
  */
 package org.apache.syncope.client.console.rest;
 
-import static org.apache.syncope.client.console.rest.BaseRestClient.getService;
-
 import java.util.List;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.Response;

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java
index 0c96627..e65ce7e 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java
@@ -54,6 +54,10 @@ public class ReportRestClient extends BaseRestClient
         return getService(ReportService.class).list();
     }
 
+    public JobTO getJob(final String key) {
+        return getService(ReportService.class).getJob(key);
+    }
+
     public List<JobTO> listJobs() {
         return getService(ReportService.class).listJobs();
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
index d184085..723ae2e 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
@@ -47,6 +47,10 @@ public class TaskRestClient extends BaseRestClient implements ExecutionRestClien
 
     private static final long serialVersionUID = 6284485820911028843L;
 
+    public JobTO getJob(final String key) {
+        return getService(TaskService.class).getJob(key);
+    }
+
     public List<JobTO> listJobs() {
         return getService(TaskService.class).listJobs();
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
index 07c2ef3..f7f0b2e 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
@@ -23,18 +23,32 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
+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;
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.widgets.JobActionPanel;
+import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.to.ProvisioningTaskTO;
 import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.markup.html.WebPage;
+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.
@@ -63,6 +77,17 @@ public abstract class ProvisioningTaskDirectoryPanel<T extends ProvisioningTaskT
 
         // super in order to call the parent implementation
         super.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);
+            }
+        });
     }
 
     @Override
@@ -103,9 +128,45 @@ public abstract class ProvisioningTaskDirectoryPanel<T extends ProvisioningTaskT
         columns.add(new BooleanPropertyColumn<>(
                 new StringResourceModel("active", this), "active", "active"));
 
+        columns.add(new AbstractColumn<T, String>(new Model<>(""), "running") {
+
+            private static final long serialVersionUID = -4008579357070833846L;
+
+            @Override
+            public void populateItem(
+                    final Item<ICellPopulator<T>> cellItem,
+                    final String componentId,
+                    final IModel<T> rowModel) {
+
+                JobTO jobTO = restClient.getJob(rowModel.getObject().getKey());
+                JobActionPanel panel = new JobActionPanel(
+                        componentId, jobTO, false, ProvisioningTaskDirectoryPanel.this, pageRef);
+                MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE,
+                        String.format("%s,%s",
+                                StandardEntitlement.TASK_EXECUTE,
+                                StandardEntitlement.TASK_UPDATE));
+                cellItem.add(panel);
+            }
+
+            @Override
+            public String getCssClass() {
+                return "col-xs-1";
+            }
+        });
+
         return columns;
     }
 
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof JobActionPanel.JobActionPayload) {
+            container.modelChanged();
+            JobActionPanel.JobActionPayload.class.cast(event.getPayload()).getTarget().add(container);
+        } else {
+            super.onEvent(event);
+        }
+    }
+
     protected class ProvisioningTasksProvider<T extends ProvisioningTaskTO> extends SchedTasksProvider<T> {
 
         private static final long serialVersionUID = 4725679400450513556L;

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
index 82665f8..5e03e2d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
@@ -18,6 +18,9 @@
  */
 package org.apache.syncope.client.console.widgets;
 
+import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverBehavior;
+import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverConfig;
+import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig;
 import java.io.Serializable;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
@@ -30,10 +33,13 @@ import org.apache.syncope.client.console.wicket.ajax.markup.html.IndicatorAjaxLi
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
 import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.types.JobAction;
+import org.apache.wicket.Component;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.model.Model;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,14 +58,21 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> {
     public JobActionPanel(
             final String id,
             final JobTO jobTO,
-            final JobWidget widget,
+            final boolean showNotRunning,
+            final Component container,
             final PageReference pageRef) {
+
         super(id, true);
         setOutputMarkupId(true);
 
         Fragment controls;
         if (jobTO.isRunning()) {
             controls = new Fragment("controls", "runningFragment", this);
+            controls.add(new Label("status", Model.of()).add(new PopoverBehavior(
+                    Model.<String>of(),
+                    Model.of("<pre>" + (jobTO.getStatus() == null ? StringUtils.EMPTY : jobTO.getStatus()) + "</pre>"),
+                    new PopoverConfig().withAnimation(true).withHoverTrigger().withHtml(true).
+                            withPlacement(TooltipConfig.Placement.left))));
             controls.add(new IndicatorAjaxLink<Void>("stop") {
 
                 private static final long serialVersionUID = -7978723352517770644L;
@@ -83,7 +96,7 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> {
                             default:
                         }
                         SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
-                        send(widget, Broadcast.EXACT, new JobActionPayload(target));
+                        send(container, Broadcast.EXACT, new JobActionPayload(target));
                     } catch (Exception e) {
                         LOG.error("While stopping {}", jobTO.getRefDesc(), e);
                         SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName()
@@ -117,7 +130,7 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> {
                             default:
                         }
                         SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
-                        send(widget, Broadcast.EXACT, new JobActionPayload(target));
+                        send(container, Broadcast.EXACT, new JobActionPayload(target));
                     } catch (Exception e) {
                         LOG.error("While starting {}", jobTO.getRefDesc(), e);
                         SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName()
@@ -126,6 +139,10 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> {
                     ((BasePage) getPage()).getNotificationPanel().refresh(target);
                 }
             });
+            if (!showNotRunning) {
+                controls.setOutputMarkupPlaceholderTag(true);
+                controls.setVisible(false);
+            }
         }
         addInnerObject(controls);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
index 5441b13..8575079 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
@@ -357,9 +357,9 @@ public class JobWidget extends BaseWidget {
                         final IModel<JobTO> rowModel) {
 
                     JobTO jobTO = rowModel.getObject();
-                    JobActionPanel panel = new JobActionPanel(componentId, jobTO, JobWidget.this, pageRef);
+                    JobActionPanel panel = new JobActionPanel(componentId, jobTO, true, JobWidget.this, pageRef);
                     MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE,
-                            String.format("%s,%s%s,%s",
+                            String.format("%s,%s,%s,%s",
                                     StandardEntitlement.TASK_EXECUTE,
                                     StandardEntitlement.REPORT_EXECUTE,
                                     StandardEntitlement.TASK_UPDATE,

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
index 294c978..c2dcf2c 100644
--- a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
+++ b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
@@ -879,6 +879,10 @@ li.todoitem a {
   cursor: default;
 }
 
+.popover{
+    max-width: 100%;
+}
+
 #popover:hover {
   cursor: pointer;
 }
@@ -1157,4 +1161,4 @@ div#inline-actions ul.menu i, div#tablehandling ul.menu i {
 
 div#tablehandling ul.menu li a {
   padding: 0px !important;
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html
index e94f292..8e31f36 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html
@@ -22,6 +22,8 @@ under the License.
     <wicket:fragment wicket:id="runningFragment">
       <i id="actionLink" class="fa fa-refresh fa-spin"></i>
       &nbsp;
+      <div wicket:id="status" class="fa fa-binoculars"/>
+      &nbsp;
       <a href="#" wicket:id="stop" class="fa fa-stop-circle"></a>
     </wicket:fragment>
     <wicket:fragment wicket:id="notRunningFragment">

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java
index 2cb5690..cee8db2 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java
@@ -42,6 +42,8 @@ public class JobTO extends AbstractBaseBean {
 
     private Date start;
 
+    private String status;
+
     public JobType getType() {
         return type;
     }
@@ -93,4 +95,13 @@ public class JobTO extends AbstractBaseBean {
                 ? null
                 : new Date(start.getTime());
     }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(final String status) {
+        this.status = status;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java
index 2b09d49..16bc38f 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java
@@ -101,6 +101,17 @@ public interface ExecutableService extends JAXRSService {
     ExecTO execute(@BeanParam ExecuteQuery query);
 
     /**
+     * Returns job (running or scheduled) for the executable matching the given key.
+     *
+     * @param key executable key
+     * @return job (running or scheduled) for the given key
+     */
+    @GET
+    @Path("jobs/{key}")
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    JobTO getJob(@PathParam("key") String key);
+
+    /**
      * List jobs (running and / or scheduled).
      *
      * @return jobs (running and / or scheduled)

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java
index d1b7cb3..6b34bc7 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java
@@ -42,6 +42,8 @@ public abstract class AbstractExecutableLogic<T extends AbstractBaseBean> extend
     public abstract BulkActionResult deleteExecutions(
             String key, Date startedBefore, Date startedAfter, Date endedBefore, Date endedAfter);
 
+    public abstract JobTO getJob(String key);
+
     public abstract List<JobTO> listJobs();
 
     public abstract void actionJob(String key, JobAction action);

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
index a93ae2d..844d353 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
@@ -26,11 +26,14 @@ import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.types.JobAction;
 import org.apache.syncope.common.lib.types.JobType;
 import org.apache.syncope.core.provisioning.api.job.JobManager;
+import org.apache.syncope.core.provisioning.java.job.AbstractInterruptableJob;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.quartz.JobKey;
 import org.quartz.Scheduler;
 import org.quartz.SchedulerException;
 import org.quartz.Trigger;
 import org.quartz.impl.matchers.GroupMatcher;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 
@@ -44,32 +47,54 @@ abstract class AbstractJobLogic<T extends AbstractBaseBean> extends AbstractTran
 
     protected abstract Triple<JobType, String, String> getReference(final JobKey jobKey);
 
-    protected List<JobTO> doListJobs() {
-        List<JobTO> jobTOs = new ArrayList<>();
+    protected JobTO getJobTO(final JobKey jobKey) throws SchedulerException {
+        JobTO jobTO = null;
 
-        try {
-            for (JobKey jobKey : scheduler.getScheduler().
-                    getJobKeys(GroupMatcher.jobGroupEquals(Scheduler.DEFAULT_GROUP))) {
+        Triple<JobType, String, String> reference = getReference(jobKey);
+        if (reference != null) {
+            jobTO = new JobTO();
 
-                JobTO jobTO = new JobTO();
+            jobTO.setType(reference.getLeft());
+            jobTO.setRefKey(reference.getMiddle());
+            jobTO.setRefDesc(reference.getRight());
 
-                Triple<JobType, String, String> reference = getReference(jobKey);
-                if (reference != null) {
-                    jobTOs.add(jobTO);
+            List<? extends Trigger> jobTriggers = scheduler.getScheduler().getTriggersOfJob(jobKey);
+            if (jobTriggers.isEmpty()) {
+                jobTO.setScheduled(false);
+            } else {
+                jobTO.setScheduled(true);
+                jobTO.setStart(jobTriggers.get(0).getStartTime());
+            }
+
+            jobTO.setRunning(jobManager.isRunning(jobKey));
 
-                    jobTO.setType(reference.getLeft());
-                    jobTO.setRefKey(reference.getMiddle());
-                    jobTO.setRefDesc(reference.getRight());
+            jobTO.setStatus("UNKNOWN");
+            if (jobTO.isRunning()) {
+                try {
+                    Object job = ApplicationContextProvider.getBeanFactory().getBean(jobKey.getName());
+                    if (job instanceof AbstractInterruptableJob
+                            && ((AbstractInterruptableJob) job).getDelegate() != null) {
 
-                    List<? extends Trigger> jobTriggers = scheduler.getScheduler().getTriggersOfJob(jobKey);
-                    if (jobTriggers.isEmpty()) {
-                        jobTO.setScheduled(false);
-                    } else {
-                        jobTO.setScheduled(true);
-                        jobTO.setStart(jobTriggers.get(0).getStartTime());
+                        jobTO.setStatus(((AbstractInterruptableJob) job).getDelegate().currentStatus());
                     }
+                } catch (NoSuchBeanDefinitionException e) {
+                    LOG.warn("Could not find job {} implementation", jobKey, e);
+                }
+            }
+        }
+
+        return jobTO;
+    }
+
+    protected List<JobTO> doListJobs() {
+        List<JobTO> jobTOs = new ArrayList<>();
+        try {
+            for (JobKey jobKey : scheduler.getScheduler().
+                    getJobKeys(GroupMatcher.jobGroupEquals(Scheduler.DEFAULT_GROUP))) {
 
-                    jobTO.setRunning(jobManager.isRunning(jobKey));
+                JobTO jobTO = getJobTO(jobKey);
+                if (jobTO != null) {
+                    jobTOs.add(jobTO);
                 }
             }
         } catch (SchedulerException e) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
index 534a537..6a5a442 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
@@ -64,6 +64,7 @@ import org.apache.syncope.core.provisioning.api.data.ReportDataBinder;
 import org.apache.syncope.core.provisioning.api.job.JobNamer;
 import org.apache.xmlgraphics.util.MimeConstants;
 import org.quartz.JobKey;
+import org.quartz.SchedulerException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
@@ -368,6 +369,30 @@ public class ReportLogic extends AbstractExecutableLogic<ReportTO> {
         return super.doListJobs();
     }
 
+    @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_READ + "')")
+    @Override
+    public JobTO getJob(final String key) {
+        Report report = reportDAO.find(key);
+        if (report == null) {
+            throw new NotFoundException("Report " + key);
+        }
+
+        JobTO jobTO = null;
+        try {
+            jobTO = getJobTO(JobNamer.getJobKey(report));
+        } catch (SchedulerException e) {
+            LOG.error("Problems while retrieving scheduled job {}", JobNamer.getJobKey(report), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+        if (jobTO == null) {
+            throw new NotFoundException("Job for report " + key);
+        }
+        return jobTO;
+    }
+
     @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_EXECUTE + "')")
     @Override
     public void actionJob(final String key, final JobAction action) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
index a9ac3d3..baad7e8 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
@@ -55,10 +55,11 @@ import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecu
 import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.NotificationDAO;
+import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate;
 import org.apache.syncope.core.provisioning.java.job.TaskJob;
-import org.apache.syncope.core.provisioning.java.job.notification.NotificationJobDelegate;
 import org.quartz.JobDataMap;
 import org.quartz.JobKey;
+import org.quartz.SchedulerException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
@@ -385,6 +386,30 @@ public class TaskLogic extends AbstractExecutableLogic<TaskTO> {
         return super.doListJobs();
     }
 
+    @PreAuthorize("hasRole('" + StandardEntitlement.TASK_READ + "')")
+    @Override
+    public JobTO getJob(final String key) {
+        Task task = taskDAO.find(key);
+        if (task == null) {
+            throw new NotFoundException("Task " + key);
+        }
+
+        JobTO jobTO = null;
+        try {
+            jobTO = getJobTO(JobNamer.getJobKey(task));
+        } catch (SchedulerException e) {
+            LOG.error("Problems while retrieving scheduled job {}", JobNamer.getJobKey(task), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+        if (jobTO == null) {
+            throw new NotFoundException("Job for task " + key);
+        }
+        return jobTO;
+    }
+
     @PreAuthorize("hasRole('" + StandardEntitlement.TASK_EXECUTE + "')")
     @Override
     public void actionJob(final String key, final JobAction action) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java
index ab05f4a..2fb2a86 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.persistence.api.dao;
 
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.common.lib.report.ReportletConf;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
@@ -41,7 +42,8 @@ public interface Reportlet {
      * Actual data extraction for reporting.
      *
      * @param handler SAX content handler for streaming result
+     * @param status current report status (for job reporting)
      * @throws SAXException if there is any problem in SAX handling
      */
-    void extract(ContentHandler handler) throws SAXException;
+    void extract(ContentHandler handler, AtomicReference<String> status) throws SAXException;
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
index 7b84fc5..a0b21cb 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.api;
 
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.identityconnectors.framework.common.objects.Attribute;
@@ -63,7 +64,7 @@ public interface Connector {
             ObjectClass objectClass,
             Set<Attribute> attrs,
             OperationOptions options,
-            Boolean[] propagationAttempted);
+            AtomicReference<Boolean> propagationAttempted);
 
     /**
      * Update user / group on a connector instance.
@@ -80,7 +81,7 @@ public interface Connector {
             Uid uid,
             Set<Attribute> attrs,
             OperationOptions options,
-            Boolean[] propagationAttempted);
+            AtomicReference<Boolean> propagationAttempted);
 
     /**
      * Delete user / group on a connector instance.
@@ -94,7 +95,7 @@ public interface Connector {
             ObjectClass objectClass,
             Uid uid,
             OperationOptions options,
-            Boolean[] propagationAttempted);
+            AtomicReference<Boolean> propagationAttempted);
 
     /**
      * Fetches all remote objects (for use during full reconciliation).

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java
new file mode 100644
index 0000000..3bfa292
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java
@@ -0,0 +1,27 @@
+/*
+ * 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.core.provisioning.api.job;
+
+/**
+ * Implementations of this interface will perform the actual operations required to Quartz's {@link org.quartz.Job}.
+ */
+public interface JobDelegate {
+
+    String currentStatus();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java
index a03f36b..bb69b10 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java
@@ -21,7 +21,7 @@ package org.apache.syncope.core.provisioning.api.job;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 
-public interface SchedTaskJobDelegate {
+public interface SchedTaskJobDelegate extends JobDelegate {
 
     void execute(String taskKey, boolean dryRun, JobExecutionContext context) throws JobExecutionException;
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java
new file mode 100644
index 0000000..bbf455f
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java
@@ -0,0 +1,27 @@
+/*
+ * 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.core.provisioning.api.job.report;
+
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
+import org.quartz.JobExecutionException;
+
+public interface ReportJobDelegate extends JobDelegate {
+
+    void execute(String reportKey) throws JobExecutionException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java
new file mode 100644
index 0000000..3dfcddd
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java
@@ -0,0 +1,31 @@
+/*
+ * 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.core.provisioning.api.notification;
+
+import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
+import org.quartz.JobExecutionException;
+
+public interface NotificationJobDelegate extends JobDelegate {
+
+    TaskExec executeSingle(NotificationTask task);
+
+    void execute() throws JobExecutionException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java
index ab02282..39eed32 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java
@@ -18,10 +18,13 @@
  */
 package org.apache.syncope.core.provisioning.api.pushpull;
 
+import org.identityconnectors.framework.common.objects.Name;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.identityconnectors.framework.common.objects.SyncToken;
 
 public interface SyncopePullExecutor {
 
     void setLatestSyncToken(ObjectClass objectClass, SyncToken latestSyncToken);
+
+    void reportHandled(ObjectClass objectClass, Name name);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
index 90645b2..523ecc0 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
@@ -25,6 +25,7 @@ import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.common.lib.types.ConnectorCapability;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
@@ -162,12 +163,12 @@ public class ConnectorFacadeProxy implements Connector {
             final ObjectClass objectClass,
             final Set<Attribute> attrs,
             final OperationOptions options,
-            final Boolean[] propagationAttempted) {
+            final AtomicReference<Boolean> propagationAttempted) {
 
         Uid result = null;
 
         if (connInstance.getCapabilities().contains(ConnectorCapability.CREATE)) {
-            propagationAttempted[0] = true;
+            propagationAttempted.set(true);
 
             Future<Uid> future = asyncFacade.create(connector, objectClass, attrs, options);
             try {
@@ -197,12 +198,12 @@ public class ConnectorFacadeProxy implements Connector {
             final Uid uid,
             final Set<Attribute> attrs,
             final OperationOptions options,
-            final Boolean[] propagationAttempted) {
+            final AtomicReference<Boolean> propagationAttempted) {
 
         Uid result = null;
 
         if (connInstance.getCapabilities().contains(ConnectorCapability.UPDATE)) {
-            propagationAttempted[0] = true;
+            propagationAttempted.set(true);
 
             Future<Uid> future = asyncFacade.update(connector, objectClass, uid, attrs, options);
 
@@ -233,10 +234,10 @@ public class ConnectorFacadeProxy implements Connector {
             final ObjectClass objectClass,
             final Uid uid,
             final OperationOptions options,
-            final Boolean[] propagationAttempted) {
+            final AtomicReference<Boolean> propagationAttempted) {
 
         if (connInstance.getCapabilities().contains(ConnectorCapability.DELETE)) {
-            propagationAttempted[0] = true;
+            propagationAttempted.set(true);
 
             Future<Uid> future = asyncFacade.delete(connector, objectClass, uid, options);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java
index 19bbf1e..86a7f49 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.java.job;
 
 import java.util.Date;
 import java.util.concurrent.atomic.AtomicReference;
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
 import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
 import org.apache.syncope.core.provisioning.api.job.JobManager;
 import org.quartz.DisallowConcurrentExecution;
@@ -40,13 +41,25 @@ public abstract class AbstractInterruptableJob implements InterruptableJob {
      */
     private final AtomicReference<Thread> runningThread = new AtomicReference<>();
 
+    private final JobDelegate embeddedDelegate = new JobDelegate() {
+
+        @Override
+        public String currentStatus() {
+            return "RUNNING THREAD: " + runningThread.get();
+        }
+    };
+
     private long interruptMaxRetries = 1;
 
+    public JobDelegate getDelegate() {
+        return embeddedDelegate;
+    }
+
     @Override
     public void execute(final JobExecutionContext context) throws JobExecutionException {
-        this.runningThread.set(Thread.currentThread());
+        runningThread.set(Thread.currentThread());
         try {
-            this.interruptMaxRetries = context.getMergedJobDataMap().getLong(JobManager.INTERRUPT_MAX_RETRIES_KEY);
+            interruptMaxRetries = context.getMergedJobDataMap().getLong(JobManager.INTERRUPT_MAX_RETRIES_KEY);
         } catch (Exception e) {
             LOG.debug("Could not set {}, defaults to {}", JobManager.INTERRUPT_MAX_RETRIES_KEY, interruptMaxRetries, e);
         }
@@ -54,7 +67,7 @@ public abstract class AbstractInterruptableJob implements InterruptableJob {
 
     @Override
     public void interrupt() throws UnableToInterruptJobException {
-        Thread thread = this.runningThread.getAndSet(null);
+        Thread thread = runningThread.getAndSet(null);
         if (thread == null) {
             LOG.warn("Unable to retrieve the thread of the current job execution");
         } else {
@@ -68,7 +81,7 @@ public abstract class AbstractInterruptableJob implements InterruptableJob {
             }
             // if the thread is still alive, it should be available in the next stop
             if (thread.isAlive()) {
-                this.runningThread.set(thread);
+                runningThread.set(thread);
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java
index 5fc5405..7103b7b 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.provisioning.java.job;
 
 import java.util.Date;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
 import org.apache.syncope.core.persistence.api.dao.TaskDAO;
@@ -72,6 +73,13 @@ public abstract class AbstractSchedTaskJobDelegate implements SchedTaskJobDelega
     @Autowired
     protected AuditManager auditManager;
 
+    protected final AtomicReference<String> status = new AtomicReference<>();
+
+    @Override
+    public String currentStatus() {
+        return status.get();
+    }
+
     @Transactional
     @Override
     public void execute(final String taskKey, final boolean dryRun, final JobExecutionContext context)
@@ -90,6 +98,8 @@ public abstract class AbstractSchedTaskJobDelegate implements SchedTaskJobDelega
         execution.setStart(new Date());
         execution.setTask(task);
 
+        status.set("Initialization completed");
+
         AuditElements.Result result;
 
         try {
@@ -110,6 +120,8 @@ public abstract class AbstractSchedTaskJobDelegate implements SchedTaskJobDelega
         }
         task = taskDAO.save(task);
 
+        status.set("Done");
+
         notificationManager.createTasks(
                 AuditElements.EventCategoryType.TASK,
                 this.getClass().getSimpleName(),

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
index 4868bbc..4f1a1eb 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
@@ -82,20 +82,25 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
         }
         result.append("provision\n\n");
 
+        status.set(result.toString());
+
         MembershipCond membershipCond = new MembershipCond();
         membershipCond.setGroup(groupKey);
         List<User> users = searchDAO.search(SearchCond.getLeafCond(membershipCond), AnyTypeKind.USER);
         Collection<String> groupResourceKeys = groupDAO.findAllResourceKeys(groupKey);
+        status.set("About to "
+                + (actionType == BulkMembersActionType.DEPROVISION ? "de" : "") + "provision "
+                + users.size() + " users from " + groupResourceKeys);
         for (User user : users) {
             List<PropagationStatus> statuses = actionType == BulkMembersActionType.DEPROVISION
                     ? userProvisioningManager.deprovision(user.getKey(), groupResourceKeys, false)
                     : userProvisioningManager.provision(user.getKey(), true, null, groupResourceKeys, false);
-            for (PropagationStatus status : statuses) {
+            for (PropagationStatus propagationStatus : statuses) {
                 result.append("User ").append(user.getKey()).append('\t').
-                        append("Resource ").append(status.getResource()).append('\t').
-                        append(status.getStatus());
-                if (StringUtils.isNotBlank(status.getFailureReason())) {
-                    result.append('\n').append(status.getFailureReason()).append('\n');
+                        append("Resource ").append(propagationStatus.getResource()).append('\t').
+                        append(propagationStatus.getStatus());
+                if (StringUtils.isNotBlank(propagationStatus.getFailureReason())) {
+                    result.append('\n').append(propagationStatus.getFailureReason()).append('\n');
                 }
                 result.append("\n");
             }
@@ -105,17 +110,20 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
         membershipCond = new MembershipCond();
         membershipCond.setGroup(groupKey);
         List<AnyObject> anyObjects = searchDAO.search(SearchCond.getLeafCond(membershipCond), AnyTypeKind.ANY_OBJECT);
+        status.set("About to "
+                + (actionType == BulkMembersActionType.DEPROVISION ? "de" : "") + "provision "
+                + anyObjects.size() + " any objects from " + groupResourceKeys);
         for (AnyObject anyObject : anyObjects) {
             List<PropagationStatus> statuses = actionType == BulkMembersActionType.DEPROVISION
                     ? anyObjectProvisioningManager.deprovision(anyObject.getKey(), groupResourceKeys, false)
                     : anyObjectProvisioningManager.provision(anyObject.getKey(), groupResourceKeys, false);
 
-            for (PropagationStatus status : statuses) {
+            for (PropagationStatus propagationStatus : statuses) {
                 result.append(anyObject.getType().getKey()).append(' ').append(anyObject.getKey()).append('\t').
-                        append("Resource ").append(status.getResource()).append('\t').
-                        append(status.getStatus());
-                if (StringUtils.isNotBlank(status.getFailureReason())) {
-                    result.append('\n').append(status.getFailureReason()).append('\n');
+                        append("Resource ").append(propagationStatus.getResource()).append('\t').
+                        append(propagationStatus.getStatus());
+                if (StringUtils.isNotBlank(propagationStatus.getFailureReason())) {
+                    result.append('\n').append(propagationStatus.getFailureReason()).append('\n');
                 }
                 result.append("\n");
             }

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
index f9d310f..60a1956 100755
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
@@ -89,8 +89,15 @@ public class IdentityRecertification extends AbstractSchedTaskJobDelegate {
             return "DRY RUN";
         }
 
+        int total = userDAO.count();
+        int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+        status.set("Processing " + total + " users in " + pages + " pages");
+
         long now = System.currentTimeMillis();
-        for (int page = 1; page <= (userDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+        for (int page = 1; page <= pages; page++) {
+            status.set("Processing " + total + " users: page " + page + " of " + pages);
+
             for (User user : userDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE)) {
                 LOG.debug("Processing user: {}", user.getUsername());
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
index 6f926cf..c2af5e5 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.java.job;
 
 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate;
@@ -53,6 +54,8 @@ public class TaskJob extends AbstractInterruptableJob {
      */
     private String taskKey;
 
+    private SchedTaskJobDelegate delegate;
+
     /**
      * Task key setter.
      *
@@ -63,6 +66,11 @@ public class TaskJob extends AbstractInterruptableJob {
     }
 
     @Override
+    public JobDelegate getDelegate() {
+        return delegate;
+    }
+
+    @Override
     public void execute(final JobExecutionContext context) throws JobExecutionException {
         super.execute(context);
 
@@ -72,16 +80,17 @@ public class TaskJob extends AbstractInterruptableJob {
                         try {
                             ImplementationDAO implementationDAO =
                             ApplicationContextProvider.getApplicationContext().getBean(ImplementationDAO.class);
-                            Implementation taskJobDelegate = implementationDAO.find(
+                            Implementation implementation = implementationDAO.find(
                                     context.getMergedJobDataMap().getString(DELEGATE_IMPLEMENTATION));
-                            if (taskJobDelegate == null) {
+                            if (implementation == null) {
                                 LOG.error("Could not find Implementation '{}', aborting",
                                         context.getMergedJobDataMap().getString(DELEGATE_IMPLEMENTATION));
                             } else {
-                                ImplementationManager.<SchedTaskJobDelegate>build(taskJobDelegate).
-                                        execute(taskKey,
-                                                context.getMergedJobDataMap().getBoolean(DRY_RUN_JOBDETAIL_KEY),
-                                                context);
+                                delegate = ImplementationManager.<SchedTaskJobDelegate>build(implementation);
+                                delegate.execute(
+                                        taskKey,
+                                        context.getMergedJobDataMap().getBoolean(DRY_RUN_JOBDETAIL_KEY),
+                                        context);
                             }
                         } catch (Exception e) {
                             LOG.error("While executing task {}", taskKey, e);

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java
new file mode 100644
index 0000000..7ab218b
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java
@@ -0,0 +1,296 @@
+/*
+ * 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.core.provisioning.java.job.notification;
+
+import java.io.PrintStream;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.LogOutputStream;
+import org.apache.syncope.common.lib.PropertyUtils;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.common.lib.types.TraceLevel;
+import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
+import org.apache.syncope.core.persistence.api.dao.TaskDAO;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.core.provisioning.api.AuditManager;
+import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate;
+import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+import org.apache.syncope.core.spring.security.Encryptor;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class DefaultNotificationJobDelegate implements InitializingBean, NotificationJobDelegate {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationJobDelegate.class);
+
+    @Autowired
+    private TaskDAO taskDAO;
+
+    @Autowired
+    private JavaMailSender mailSender;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Autowired
+    private AuditManager auditManager;
+
+    @Autowired
+    private NotificationManager notificationManager;
+
+    private final AtomicReference<String> status = new AtomicReference<>();
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        if (mailSender instanceof JavaMailSenderImpl) {
+            JavaMailSenderImpl javaMailSender = (JavaMailSenderImpl) mailSender;
+
+            Properties javaMailProperties = javaMailSender.getJavaMailProperties();
+
+            Properties props = PropertyUtils.read(Encryptor.class, "mail.properties", "conf.directory").getLeft();
+            for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) {
+                String prop = (String) e.nextElement();
+                if (prop.startsWith("mail.smtp.")) {
+                    javaMailProperties.setProperty(prop, props.getProperty(prop));
+                }
+            }
+
+            if (StringUtils.isNotBlank(javaMailSender.getUsername())) {
+                javaMailProperties.setProperty("mail.smtp.auth", "true");
+            }
+
+            javaMailSender.setJavaMailProperties(javaMailProperties);
+
+            String mailDebug = props.getProperty("mail.debug", "false");
+            if (BooleanUtils.toBoolean(mailDebug)) {
+                Session session = javaMailSender.getSession();
+                session.setDebug(true);
+                session.setDebugOut(new PrintStream(new LogOutputStream(LOG)));
+            }
+        }
+    }
+
+    @Override
+    public String currentStatus() {
+        return status.get();
+    }
+
+    @Transactional
+    @Override
+    public TaskExec executeSingle(final NotificationTask task) {
+        TaskExec execution = entityFactory.newEntity(TaskExec.class);
+        execution.setTask(task);
+        execution.setStart(new Date());
+
+        boolean retryPossible = true;
+
+        if (StringUtils.isBlank(task.getSubject()) || task.getRecipients().isEmpty()
+                || StringUtils.isBlank(task.getHtmlBody()) || StringUtils.isBlank(task.getTextBody())) {
+
+            String message = "Could not fetch all required information for sending e-mails:\n"
+                    + task.getRecipients() + "\n"
+                    + task.getSender() + "\n"
+                    + task.getSubject() + "\n"
+                    + task.getHtmlBody() + "\n"
+                    + task.getTextBody();
+            LOG.error(message);
+
+            execution.setStatus(NotificationJob.Status.NOT_SENT.name());
+            retryPossible = false;
+
+            if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
+                execution.setMessage(message);
+            }
+        } else {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("About to send e-mails:\n"
+                        + task.getRecipients() + "\n"
+                        + task.getSender() + "\n"
+                        + task.getSubject() + "\n"
+                        + task.getHtmlBody() + "\n"
+                        + task.getTextBody() + "\n");
+            }
+
+            status.set("Sending notifications to " + task.getRecipients());
+
+            for (String to : task.getRecipients()) {
+                try {
+                    MimeMessage message = mailSender.createMimeMessage();
+                    MimeMessageHelper helper = new MimeMessageHelper(message, true);
+                    helper.setTo(to);
+                    helper.setFrom(task.getSender());
+                    helper.setSubject(task.getSubject());
+                    helper.setText(task.getTextBody(), task.getHtmlBody());
+
+                    mailSender.send(message);
+
+                    execution.setStatus(NotificationJob.Status.SENT.name());
+
+                    StringBuilder report = new StringBuilder();
+                    switch (task.getTraceLevel()) {
+                        case ALL:
+                            report.append("FROM: ").append(task.getSender()).append('\n').
+                                    append("TO: ").append(to).append('\n').
+                                    append("SUBJECT: ").append(task.getSubject()).append('\n').append('\n').
+                                    append(task.getTextBody()).append('\n').append('\n').
+                                    append(task.getHtmlBody()).append('\n');
+                            break;
+
+                        case SUMMARY:
+                            report.append("E-mail sent to ").append(to).append('\n');
+                            break;
+
+                        case FAILURES:
+                        case NONE:
+                        default:
+                    }
+                    if (report.length() > 0) {
+                        execution.setMessage(report.toString());
+                    }
+
+                    notificationManager.createTasks(
+                            AuditElements.EventCategoryType.TASK,
+                            "notification",
+                            null,
+                            "send",
+                            AuditElements.Result.SUCCESS,
+                            null,
+                            null,
+                            task,
+                            "Successfully sent notification to " + to);
+                } catch (Exception e) {
+                    LOG.error("Could not send e-mail", e);
+
+                    execution.setStatus(NotificationJob.Status.NOT_SENT.name());
+                    if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
+                        execution.setMessage(ExceptionUtils2.getFullStackTrace(e));
+                    }
+
+                    notificationManager.createTasks(
+                            AuditElements.EventCategoryType.TASK,
+                            "notification",
+                            null,
+                            "send",
+                            AuditElements.Result.FAILURE,
+                            null,
+                            null,
+                            task,
+                            "Could not send notification to " + to, e);
+                }
+
+                execution.setEnd(new Date());
+            }
+        }
+
+        if (hasToBeRegistered(execution)) {
+            execution = notificationManager.storeExec(execution);
+            if (retryPossible
+                    && (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT)) {
+
+                handleRetries(execution);
+            }
+        } else {
+            notificationManager.setTaskExecuted(execution.getTask().getKey(), true);
+        }
+
+        return execution;
+    }
+
+    @Transactional
+    @Override
+    public void execute() throws JobExecutionException {
+        List<NotificationTask> tasks = taskDAO.<NotificationTask>findToExec(TaskType.NOTIFICATION);
+
+        status.set("Sending out " + tasks.size() + " notifications");
+
+        for (NotificationTask task : tasks) {
+            LOG.debug("Found notification task {} to be executed: starting...", task);
+            executeSingle(task);
+            LOG.debug("Notification task {} executed", task);
+        }
+    }
+
+    private boolean hasToBeRegistered(final TaskExec execution) {
+        NotificationTask task = (NotificationTask) execution.getTask();
+
+        // True if either failed and failures have to be registered, or if ALL
+        // has to be registered.
+        return (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT
+                && task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
+                || task.getTraceLevel() == TraceLevel.ALL;
+    }
+
+    private void handleRetries(final TaskExec execution) {
+        if (notificationManager.getMaxRetries() <= 0) {
+            return;
+        }
+
+        long failedExecutionsCount = notificationManager.countExecutionsWithStatus(
+                execution.getTask().getKey(), NotificationJob.Status.NOT_SENT.name());
+
+        if (failedExecutionsCount <= notificationManager.getMaxRetries()) {
+            LOG.debug("Execution of notification task {} will be retried [{}/{}]",
+                    execution.getTask(), failedExecutionsCount, notificationManager.getMaxRetries());
+            notificationManager.setTaskExecuted(execution.getTask().getKey(), false);
+
+            auditManager.audit(
+                    AuditElements.EventCategoryType.TASK,
+                    "notification",
+                    null,
+                    "retry",
+                    AuditElements.Result.SUCCESS,
+                    null,
+                    null,
+                    execution,
+                    "Notification task " + execution.getTask().getKey() + " will be retried");
+        } else {
+            LOG.error("Maximum number of retries reached for task {} - giving up", execution.getTask());
+
+            auditManager.audit(
+                    AuditElements.EventCategoryType.TASK,
+                    "notification",
+                    null,
+                    "retry",
+                    AuditElements.Result.FAILURE,
+                    null,
+                    null,
+                    execution,
+                    "Giving up retries on notification task " + execution.getTask().getKey());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java
index 13d45b0..0137e8f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java
@@ -20,6 +20,8 @@ package org.apache.syncope.core.provisioning.java.job.notification;
 
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.persistence.api.DomainsHolder;
+import org.apache.syncope.core.provisioning.api.job.JobDelegate;
+import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate;
 import org.apache.syncope.core.provisioning.java.job.AbstractInterruptableJob;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
@@ -54,6 +56,11 @@ public class NotificationJob extends AbstractInterruptableJob {
     private NotificationJobDelegate delegate;
 
     @Override
+    public JobDelegate getDelegate() {
+        return delegate;
+    }
+
+    @Override
     public void execute(final JobExecutionContext context) throws JobExecutionException {
         super.execute(context);
 


[08/11] syncope git commit: Small improvements to build-tools, including Swagger

Posted by il...@apache.org.
Small improvements to build-tools, including Swagger


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/e073cc43
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/e073cc43
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/e073cc43

Branch: refs/heads/master
Commit: e073cc43225bbeb4c8ea670750b8f5a0829d6676
Parents: d505627
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Thu Mar 1 15:47:13 2018 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Mar 2 12:13:18 2018 +0100

----------------------------------------------------------------------
 fit/build-tools/pom.xml                         | 16 ++++++++++++-
 .../syncope/fit/buildtools/cxf/UserService.java |  3 ++-
 .../fit/buildtools/cxf/UserServiceImpl.java     | 13 ++++++++--
 .../META-INF/cxf/org.apache.cxf.Logger          |  1 +
 .../src/main/resources/cxfContext.xml           | 25 ++++++++++++++++++++
 5 files changed, 54 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/e073cc43/fit/build-tools/pom.xml
----------------------------------------------------------------------
diff --git a/fit/build-tools/pom.xml b/fit/build-tools/pom.xml
index 9f0d783..a4172e7 100644
--- a/fit/build-tools/pom.xml
+++ b/fit/build-tools/pom.xml
@@ -45,6 +45,11 @@ under the License.
     </dependency>
     
     <dependency>
+      <groupId>javax.validation</groupId>
+      <artifactId>validation-api</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.directory.server</groupId>
       <artifactId>apacheds-all</artifactId>
     </dependency>
@@ -88,13 +93,22 @@ under the License.
     <dependency>
       <groupId>org.apache.cxf</groupId>
       <artifactId>cxf-rt-rs-service-description</artifactId>
-    </dependency>    
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-service-description-openapi-v3</artifactId>
+    </dependency>
     <dependency>
       <groupId>com.fasterxml.jackson.jaxrs</groupId>
       <artifactId>jackson-jaxrs-json-provider</artifactId>
     </dependency>
 
     <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>swagger-ui</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-core</artifactId>
       <version>${spring.version}</version>

http://git-wip-us.apache.org/repos/asf/syncope/blob/e073cc43/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java
----------------------------------------------------------------------
diff --git a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java
index 5d64cfc..f02cae4 100644
--- a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java
+++ b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java
@@ -30,6 +30,7 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
 
 @Path("users")
 public interface UserService {
@@ -44,7 +45,7 @@ public interface UserService {
 
     @POST
     @Consumes({ MediaType.APPLICATION_JSON })
-    void create(User user);
+    Response create(User user);
 
     @PUT
     @Path("{key}")

http://git-wip-us.apache.org/repos/asf/syncope/blob/e073cc43/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java
----------------------------------------------------------------------
diff --git a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java
index dea0f63..6b04515 100644
--- a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java
+++ b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java
@@ -23,8 +23,12 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import javax.ws.rs.ClientErrorException;
 import javax.ws.rs.ForbiddenException;
 import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -32,6 +36,9 @@ public class UserServiceImpl implements UserService {
 
     private static final Map<UUID, User> USERS = new HashMap<UUID, User>();
 
+    @Context
+    private UriInfo uriInfo;
+
     @Override
     public List<User> list() {
         return new ArrayList<>(USERS.values());
@@ -47,14 +54,16 @@ public class UserServiceImpl implements UserService {
     }
 
     @Override
-    public void create(final User user) {
+    public Response create(final User user) {
         if (user.getKey() == null) {
             user.setKey(UUID.randomUUID());
         }
         if (USERS.containsKey(user.getKey())) {
-            throw new IllegalArgumentException("User already exists: " + user.getKey());
+            throw new ClientErrorException("User already exists: " + user.getKey(), Response.Status.CONFLICT);
         }
         USERS.put(user.getKey(), user);
+
+        return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getKey().toString()).build()).build();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/syncope/blob/e073cc43/fit/build-tools/src/main/resources/META-INF/cxf/org.apache.cxf.Logger
----------------------------------------------------------------------
diff --git a/fit/build-tools/src/main/resources/META-INF/cxf/org.apache.cxf.Logger b/fit/build-tools/src/main/resources/META-INF/cxf/org.apache.cxf.Logger
new file mode 100644
index 0000000..6e7bd36
--- /dev/null
+++ b/fit/build-tools/src/main/resources/META-INF/cxf/org.apache.cxf.Logger
@@ -0,0 +1 @@
+org.apache.cxf.common.logging.Slf4jLogger

http://git-wip-us.apache.org/repos/asf/syncope/blob/e073cc43/fit/build-tools/src/main/resources/cxfContext.xml
----------------------------------------------------------------------
diff --git a/fit/build-tools/src/main/resources/cxfContext.xml b/fit/build-tools/src/main/resources/cxfContext.xml
index 9a6c959..7384e55 100644
--- a/fit/build-tools/src/main/resources/cxfContext.xml
+++ b/fit/build-tools/src/main/resources/cxfContext.xml
@@ -38,6 +38,28 @@ under the License.
       
   <jaxws:endpoint id="soapProvisioning" address="/soap" implementor="#provisioningImpl"/>
   
+  <bean id="openApiCustomizer" class="org.apache.cxf.jaxrs.openapi.OpenApiCustomizer">
+    <property name="dynamicBasePath" value="true"/>
+    <property name="replaceTags" value="false"/>
+  </bean>
+  <bean id="openapiFeature" class="org.apache.cxf.jaxrs.openapi.OpenApiFeature">
+    <property name="title" value="Apache Syncope FIT Build Tools"/>
+    <property name="version" value="${syncope.version}"/>
+    <property name="description" value="Apache Syncope ${syncope.version}"/>    
+    <property name="contactName" value="The Apache Syncope community"/>    
+    <property name="contactEmail" value="dev@syncope.apache.org"/>    
+    <property name="contactUrl" value="http://syncope.apache.org"/>    
+    
+    <property name="scan" value="false"/>    
+    <property name="resourcePackages">
+      <set>
+        <value>org.apache.syncope.fit.buildtools.cxf</value>
+      </set>
+    </property>
+    
+    <property name="customizer" ref="openApiCustomizer"/>
+  </bean>
+  
   <bean id="jsonProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider"/>
   <jaxrs:server id="restProvisioning" address="/rest"
                 basePackages="org.apache.syncope.fit.buildtools.cxf" 
@@ -45,6 +67,9 @@ under the License.
     <jaxrs:providers>
       <ref bean="jsonProvider"/>
     </jaxrs:providers>
+    <jaxrs:features>
+      <ref bean="openapiFeature"/>
+    </jaxrs:features>
   </jaxrs:server>
 
 </beans>