You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by md...@apache.org on 2015/08/14 10:30:49 UTC

[20/31] syncope git commit: [SYNCOPE-652] Still several things to refine, but it starts taking shape

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/main/java/org/apache/syncope/core/logic/audit/AuditConnectionFactory.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/audit/AuditConnectionFactory.java b/core/logic/src/main/java/org/apache/syncope/core/logic/audit/AuditConnectionFactory.java
deleted file mode 100644
index b35c42d..0000000
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/audit/AuditConnectionFactory.java
+++ /dev/null
@@ -1,159 +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.logic.audit;
-
-import java.io.InputStream;
-import java.sql.Connection;
-import java.util.Properties;
-import javax.naming.Context;
-import javax.naming.InitialContext;
-import javax.rmi.PortableRemoteObject;
-import javax.sql.DataSource;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpression;
-import javax.xml.xpath.XPathFactory;
-import org.apache.commons.dbcp2.BasicDataSource;
-import org.apache.commons.io.IOUtils;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.FileSystemResource;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.support.PropertiesLoaderUtils;
-import org.springframework.jdbc.datasource.DataSourceUtils;
-import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
-import org.springframework.jdbc.datasource.init.ScriptUtils;
-import org.w3c.dom.Document;
-import org.w3c.dom.bootstrap.DOMImplementationRegistry;
-import org.w3c.dom.ls.DOMImplementationLS;
-import org.w3c.dom.ls.LSInput;
-import org.w3c.dom.ls.LSParser;
-
-/**
- * LOG4J SQL connection factory that first attempts to obtain a {@link javax.sql.DataSource} from the JNDI name
- * configured in Spring or, when not found, builds a new {@link javax.sql.DataSource DataSource} via Commons DBCP; if
- * any datasource if found, the SQL init script is used to populate the database.
- */
-public final class AuditConnectionFactory {
-
-    private static DataSource DATASOURCE;
-
-    private static final String PERSISTENCE_CONTEXT = "/persistenceContext.xml";
-
-    static {
-        // 1. Attempts to lookup for configured JNDI datasource (if present and available)
-        InputStream springConf = AuditConnectionFactory.class.getResourceAsStream(PERSISTENCE_CONTEXT);
-        String primary = null;
-        String fallback = null;
-        try {
-            DOMImplementationRegistry reg = DOMImplementationRegistry.newInstance();
-            DOMImplementationLS impl = (DOMImplementationLS) reg.getDOMImplementation("LS");
-            LSParser parser = impl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
-            LSInput lsinput = impl.createLSInput();
-            lsinput.setByteStream(springConf);
-            Document source = parser.parse(lsinput);
-
-            XPathFactory xPathfactory = XPathFactory.newInstance();
-            XPath xpath = xPathfactory.newXPath();
-
-            XPathExpression expr = xpath.compile("//*[local-name()='bean' and @id='persistenceProperties']/"
-                    + "child::*[local-name()='property' and @name='primary']/@value");
-            primary = (String) expr.evaluate(source, XPathConstants.STRING);
-            expr = xpath.compile("//*[local-name()='bean' and @id='persistenceProperties']/"
-                    + "child::*[local-name()='property' and @name='fallback']/@value");
-            fallback = (String) expr.evaluate(source, XPathConstants.STRING);
-
-            expr = xpath.compile("//*[local-name()='property' and @name='jndiName']/@value");
-            String jndiName = (String) expr.evaluate(source, XPathConstants.STRING);
-
-            Context ctx = new InitialContext();
-            Object obj = ctx.lookup(jndiName);
-
-            DATASOURCE = (DataSource) PortableRemoteObject.narrow(obj, DataSource.class);
-        } catch (Exception e) {
-            // ignore
-        } finally {
-            IOUtils.closeQuietly(springConf);
-        }
-
-        // 2. Creates Commons DBCP datasource
-        String initSQLScript = null;
-        try {
-            Resource persistenceProperties = null;
-            if (primary != null) {
-                if (primary.startsWith("file:")) {
-                    persistenceProperties = new FileSystemResource(primary.substring(5));
-                }
-                if (primary.startsWith("classpath:")) {
-                    persistenceProperties = new ClassPathResource(primary.substring(10));
-                }
-            }
-            if ((persistenceProperties == null || !persistenceProperties.exists()) && fallback != null) {
-                if (fallback.startsWith("file:")) {
-                    persistenceProperties = new FileSystemResource(fallback.substring(5));
-                }
-                if (fallback.startsWith("classpath:")) {
-                    persistenceProperties = new ClassPathResource(fallback.substring(10));
-                }
-            }
-            Properties persistence = PropertiesLoaderUtils.loadProperties(persistenceProperties);
-
-            initSQLScript = persistence.getProperty("audit.sql");
-
-            if (DATASOURCE == null) {
-                BasicDataSource bds = new BasicDataSource();
-                bds.setDriverClassName(persistence.getProperty("jpa.driverClassName"));
-                bds.setUrl(persistence.getProperty("jpa.url"));
-                bds.setUsername(persistence.getProperty("jpa.username"));
-                bds.setPassword(persistence.getProperty("jpa.password"));
-
-                bds.setLogAbandoned(true);
-                bds.setRemoveAbandonedOnBorrow(true);
-                bds.setRemoveAbandonedOnMaintenance(true);
-
-                DATASOURCE = bds;
-            }
-        } catch (Exception e) {
-            throw new IllegalStateException("Audit datasource configuration failed", e);
-        }
-
-        // 3. Initializes the chosen datasource
-        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
-        populator.setScripts(new Resource[] { new ClassPathResource("/audit/" + initSQLScript) });
-        // forces no statement separation
-        populator.setSeparator(ScriptUtils.EOF_STATEMENT_SEPARATOR);
-        Connection conn = DataSourceUtils.getConnection(DATASOURCE);
-        try {
-            populator.populate(conn);
-        } finally {
-            DataSourceUtils.releaseConnection(conn, DATASOURCE);
-        }
-    }
-
-    public static Connection getConnection() {
-        if (DATASOURCE != null) {
-            return DataSourceUtils.getConnection(DATASOURCE);
-        }
-
-        throw new IllegalStateException("Audit dataSource init failed: check logs");
-    }
-
-    private AuditConnectionFactory() {
-        // empty constructor for static utility class
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/main/java/org/apache/syncope/core/logic/init/ImplementationClassNamesLoader.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/ImplementationClassNamesLoader.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/ImplementationClassNamesLoader.java
index b22a54d..2bcd78f 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/ImplementationClassNamesLoader.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/ImplementationClassNamesLoader.java
@@ -25,9 +25,6 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.core.provisioning.api.job.PushJob;
-import org.apache.syncope.core.provisioning.api.job.SyncJob;
-import org.apache.syncope.core.provisioning.api.job.TaskJob;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.sync.PushActions;
 import org.apache.syncope.core.provisioning.api.sync.SyncActions;
@@ -35,6 +32,9 @@ import org.apache.syncope.core.provisioning.api.sync.SyncCorrelationRule;
 import org.apache.syncope.core.logic.report.Reportlet;
 import org.apache.syncope.core.persistence.api.SyncopeLoader;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator;
+import org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate;
+import org.apache.syncope.core.provisioning.java.sync.PushJobDelegate;
+import org.apache.syncope.core.provisioning.java.sync.SyncJobDelegate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.config.BeanDefinition;
@@ -52,7 +52,7 @@ public class ImplementationClassNamesLoader implements SyncopeLoader {
     public enum Type {
 
         REPORTLET,
-        TASKJOB,
+        TASKJOBDELEGATE,
         PROPAGATION_ACTIONS,
         SYNC_ACTIONS,
         PUSH_ACTIONS,
@@ -83,7 +83,7 @@ public class ImplementationClassNamesLoader implements SyncopeLoader {
 
         ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
         scanner.addIncludeFilter(new AssignableTypeFilter(Reportlet.class));
-        scanner.addIncludeFilter(new AssignableTypeFilter(TaskJob.class));
+        scanner.addIncludeFilter(new AssignableTypeFilter(SchedTaskJobDelegate.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(SyncActions.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(PushActions.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(SyncCorrelationRule.class));
@@ -102,11 +102,11 @@ public class ImplementationClassNamesLoader implements SyncopeLoader {
                     classNames.get(Type.REPORTLET).add(clazz.getName());
                 }
 
-                if (TaskJob.class.isAssignableFrom(clazz) && !isAbsractClazz
-                        && !SyncJob.class.isAssignableFrom(clazz)
-                        && !PushJob.class.isAssignableFrom(clazz)) {
+                if (SchedTaskJobDelegate.class.isAssignableFrom(clazz) && !isAbsractClazz
+                        && !SyncJobDelegate.class.isAssignableFrom(clazz)
+                        && !PushJobDelegate.class.isAssignableFrom(clazz)) {
 
-                    classNames.get(Type.TASKJOB).add(bd.getBeanClassName());
+                    classNames.get(Type.TASKJOBDELEGATE).add(bd.getBeanClassName());
                 }
 
                 if (SyncActions.class.isAssignableFrom(clazz) && !isAbsractClazz) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/main/java/org/apache/syncope/core/logic/init/JobInstanceLoaderImpl.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/JobInstanceLoaderImpl.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/JobInstanceLoaderImpl.java
index 6a8289a..3e7e90f 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/JobInstanceLoaderImpl.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/JobInstanceLoaderImpl.java
@@ -19,11 +19,13 @@
 package org.apache.syncope.core.logic.init;
 
 import java.text.ParseException;
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
@@ -37,17 +39,17 @@ import org.apache.syncope.core.persistence.api.entity.task.SyncTask;
 import org.apache.syncope.core.persistence.api.entity.task.Task;
 import org.apache.syncope.core.provisioning.api.job.JobInstanceLoader;
 import org.apache.syncope.core.provisioning.api.job.JobNamer;
-import org.apache.syncope.core.provisioning.api.job.SyncJob;
-import org.apache.syncope.core.provisioning.api.job.TaskJob;
-import org.apache.syncope.core.provisioning.api.sync.SyncActions;
 import org.apache.syncope.core.logic.notification.NotificationJob;
 import org.apache.syncope.core.logic.report.ReportJob;
+import org.apache.syncope.core.misc.security.AuthContextUtils;
 import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.SyncopeLoader;
-import org.apache.syncope.core.provisioning.api.job.PushJob;
-import org.apache.syncope.core.provisioning.java.sync.PushJobImpl;
-import org.apache.syncope.core.provisioning.java.sync.SyncJobImpl;
+import org.apache.syncope.core.persistence.api.DomainsHolder;
+import org.apache.syncope.core.provisioning.java.job.TaskJob;
+import org.apache.syncope.core.provisioning.java.sync.PushJobDelegate;
+import org.apache.syncope.core.provisioning.java.sync.SyncJobDelegate;
 import org.quartz.Job;
+import org.quartz.JobDataMap;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobKey;
 import org.quartz.Scheduler;
@@ -70,6 +72,9 @@ public class JobInstanceLoaderImpl implements JobInstanceLoader, SyncopeLoader {
     private static final Logger LOG = LoggerFactory.getLogger(JobInstanceLoader.class);
 
     @Autowired
+    private DomainsHolder domainsHolder;
+
+    @Autowired
     private SchedulerFactoryBean scheduler;
 
     @Autowired
@@ -81,7 +86,8 @@ public class JobInstanceLoaderImpl implements JobInstanceLoader, SyncopeLoader {
     @Autowired
     private ConfDAO confDAO;
 
-    private void registerJob(final String jobName, final Job jobInstance, final String cronExpression)
+    private void registerJob(
+            final String jobName, final Job jobInstance, final String cronExpression, final Map<String, Object> jobMap)
             throws SchedulerException, ParseException {
 
         synchronized (scheduler.getScheduler()) {
@@ -112,6 +118,7 @@ public class JobInstanceLoaderImpl implements JobInstanceLoader, SyncopeLoader {
         jobDetail.setName(jobName);
         jobDetail.setGroup(Scheduler.DEFAULT_GROUP);
         jobDetail.setJobClass(jobInstance.getClass());
+        jobDetail.setJobDataMap(new JobDataMap(jobMap));
 
         // 3. Trigger
         if (cronExpression == null) {
@@ -127,12 +134,13 @@ public class JobInstanceLoaderImpl implements JobInstanceLoader, SyncopeLoader {
         }
     }
 
-    private Job createSpringBean(final Class<?> jobClass) {
-        Job jobInstance = null;
+    @SuppressWarnings("unchecked")
+    private <T> T createSpringBean(final Class<T> jobClass) {
+        T jobInstance = null;
         for (int i = 0; i < 5 && jobInstance == null; i++) {
             LOG.debug("{} attempt to create Spring bean for {}", i, jobClass);
             try {
-                jobInstance = (Job) ApplicationContextProvider.getBeanFactory().
+                jobInstance = (T) ApplicationContextProvider.getBeanFactory().
                         createBean(jobClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
                 LOG.debug("{} attempt to create Spring bean for {} succeeded", i, jobClass);
             } catch (BeanCreationException e) {
@@ -151,75 +159,43 @@ public class JobInstanceLoaderImpl implements JobInstanceLoader, SyncopeLoader {
         return jobInstance;
     }
 
-    @SuppressWarnings("unchecked")
     @Override
-    public void registerJob(final Task task, final String jobClassName, final String cronExpression)
-            throws ClassNotFoundException, SchedulerException, ParseException {
-
-        Class<?> jobClass = Class.forName(jobClassName);
-        if (SyncJob.class.equals(jobClass)) {
-            jobClass = SyncJobImpl.class;
-        } else if (PushJob.class.equals(jobClass)) {
-            jobClass = PushJobImpl.class;
-        }
-
-        Job jobInstance = createSpringBean(jobClass);
-        if (jobInstance instanceof TaskJob) {
-            ((TaskJob) jobInstance).setTaskId(task.getKey());
-        }
-
-        // In case of synchronization job/task retrieve and set synchronization actions:
-        // actions cannot be changed at runtime but connector and synchronization policies (reloaded at execution time).
-        if (jobInstance instanceof SyncJob && task instanceof SyncTask) {
-            final List<SyncActions> actions = new ArrayList<>();
-            for (String className : ((SyncTask) task).getActionsClassNames()) {
-                try {
-                    Class<?> actionsClass = Class.forName(className);
-
-                    SyncActions syncActions = (SyncActions) ApplicationContextProvider.getBeanFactory().
-                            createBean(actionsClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
-                    actions.add(syncActions);
-                } catch (Exception e) {
-                    LOG.info("Class '{}' not found", className, e);
-                }
-            }
+    public Map<String, Object> registerJob(final SchedTask task, final long interruptMaxRetries)
+            throws SchedulerException, ParseException {
 
-            ((SyncJob) jobInstance).setActions(actions);
-        }
+        TaskJob job = createSpringBean(TaskJob.class);
+        job.setTaskKey(task.getKey());
 
-        registerJob(JobNamer.getJobName(task), jobInstance, cronExpression);
-    }
+        String jobDelegateClassName = task instanceof SyncTask
+                ? SyncJobDelegate.class.getName()
+                : task instanceof PushTask
+                        ? PushJobDelegate.class.getName()
+                        : task.getJobDelegateClassName();
 
-    @Transactional(readOnly = true)
-    @Override
-    public void registerTaskJob(final Long taskKey)
-            throws ClassNotFoundException, SchedulerException, ParseException {
+        Map<String, Object> jobMap = new HashMap<>();
+        jobMap.put(JobInstanceLoader.DOMAIN, AuthContextUtils.getDomain());
+        jobMap.put(TaskJob.DELEGATE_CLASS_KEY, jobDelegateClassName);
+        jobMap.put(TaskJob.INTERRUPT_MAX_RETRIES_KEY, interruptMaxRetries);
 
-        SchedTask task = taskDAO.find(taskKey);
-        if (task == null) {
-            throw new NotFoundException("Task " + taskKey);
-        } else {
-            registerJob(task, task.getJobClassName(), task.getCronExpression());
-        }
+        registerJob(JobNamer.getJobName(task), job, task.getCronExpression(), jobMap);
+        return jobMap;
     }
 
     @Override
     public void registerJob(final Report report) throws SchedulerException, ParseException {
-        Job jobInstance = createSpringBean(ReportJob.class);
-        ((ReportJob) jobInstance).setReportKey(report.getKey());
+        ReportJob job = createSpringBean(ReportJob.class);
+        job.setReportKey(report.getKey());
+
+        Map<String, Object> jobMap = new HashMap<>();
+        jobMap.put(JobInstanceLoader.DOMAIN, AuthContextUtils.getDomain());
 
-        registerJob(JobNamer.getJobName(report), jobInstance, report.getCronExpression());
+        registerJob(JobNamer.getJobName(report), job, report.getCronExpression(), jobMap);
     }
 
-    @Transactional(readOnly = true)
-    @Override
-    public void registerReportJob(final Long reportKey) throws SchedulerException, ParseException {
-        Report report = reportDAO.find(reportKey);
-        if (report == null) {
-            throw new NotFoundException("Report " + reportKey);
-        } else {
-            registerJob(report);
-        }
+    private void registerNotificationJob(final String cronExpression) throws SchedulerException, ParseException {
+        NotificationJob job = createSpringBean(NotificationJob.class);
+
+        registerJob("taskNotificationJob", job, cronExpression, Collections.<String, Object>emptyMap());
     }
 
     private void unregisterJob(final String jobName) {
@@ -253,41 +229,62 @@ public class JobInstanceLoaderImpl implements JobInstanceLoader, SyncopeLoader {
     @Transactional
     @Override
     public void load() {
-        // 1. jobs for SchedTasks
-        Set<SchedTask> tasks = new HashSet<>(taskDAO.<SchedTask>findAll(TaskType.SCHEDULED));
-        tasks.addAll(taskDAO.<SyncTask>findAll(TaskType.SYNCHRONIZATION));
-        tasks.addAll(taskDAO.<PushTask>findAll(TaskType.PUSH));
-        for (SchedTask task : tasks) {
+        AuthContextUtils.setFakeAuth(SyncopeConstants.MASTER_DOMAIN);
+        String notificationJobCronExpression = StringUtils.EMPTY;
+        long interruptMaxRetries = 1;
+        try {
+            CPlainAttr notificationJobCronExp =
+                    confDAO.find("notificationjob.cronExpression", NotificationJob.DEFAULT_CRON_EXP);
+            if (!notificationJobCronExp.getValuesAsStrings().isEmpty()) {
+                notificationJobCronExpression = notificationJobCronExp.getValuesAsStrings().get(0);
+            }
+
+            interruptMaxRetries = confDAO.find("tasks.interruptMaxRetries", "1").getValues().get(0).getLongValue();
+        } finally {
+            AuthContextUtils.clearFakeAuth();
+        }
+
+        for (String domain : domainsHolder.getDomains().keySet()) {
+            AuthContextUtils.setFakeAuth(domain);
+
             try {
-                registerJob(task, task.getJobClassName(), task.getCronExpression());
-            } catch (Exception e) {
-                LOG.error("While loading job instance for task " + task.getKey(), e);
+                // 1. jobs for SchedTasks
+                Set<SchedTask> tasks = new HashSet<>(taskDAO.<SchedTask>findAll(TaskType.SCHEDULED));
+                tasks.addAll(taskDAO.<SyncTask>findAll(TaskType.SYNCHRONIZATION));
+                tasks.addAll(taskDAO.<PushTask>findAll(TaskType.PUSH));
+                for (SchedTask task : tasks) {
+                    try {
+                        registerJob(task, interruptMaxRetries);
+                    } catch (Exception e) {
+                        LOG.error("While loading job instance for task " + task.getKey(), e);
+                    }
+                }
+
+                // 2. ReportJobs
+                for (Report report : reportDAO.findAll()) {
+                    try {
+                        registerJob(report);
+                    } catch (Exception e) {
+                        LOG.error("While loading job instance for report " + report.getName(), e);
+                    }
+                }
+            } finally {
+                AuthContextUtils.clearFakeAuth();
             }
         }
 
-        // 2. NotificationJob
-        CPlainAttr notificationJobCronExp =
-                confDAO.find("notificationjob.cronExpression", NotificationJob.DEFAULT_CRON_EXP);
-        if (StringUtils.isBlank(notificationJobCronExp.getValuesAsStrings().get(0))) {
+        // 3. NotificationJob
+        if (StringUtils.isBlank(notificationJobCronExpression)) {
             LOG.debug("Empty value provided for NotificationJob's cron, not registering anything on Quartz");
         } else {
             LOG.debug("NotificationJob's cron expression: {} - registering Quartz job and trigger",
-                    notificationJobCronExp);
+                    notificationJobCronExpression);
 
             try {
-                registerJob(null, NotificationJob.class.getName(), notificationJobCronExp.getValuesAsStrings().get(0));
+                registerNotificationJob(notificationJobCronExpression);
             } catch (Exception e) {
                 LOG.error("While loading NotificationJob instance", e);
             }
         }
-
-        // 3. ReportJobs
-        for (Report report : reportDAO.findAll()) {
-            try {
-                registerJob(report);
-            } catch (Exception e) {
-                LOG.error("While loading job instance for report " + report.getName(), e);
-            }
-        }
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
index 447a92f..e86b46a 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
@@ -18,19 +18,30 @@
  */
 package org.apache.syncope.core.logic.init;
 
+import java.sql.Connection;
+import java.sql.SQLException;
 import java.util.HashMap;
 import java.util.Map;
+import javax.sql.DataSource;
+import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.Appender;
 import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.db.jdbc.ColumnConfig;
+import org.apache.logging.log4j.core.appender.db.jdbc.ConnectionSource;
+import org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender;
 import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.LoggerLevel;
 import org.apache.syncope.common.lib.types.LoggerType;
+import org.apache.syncope.core.misc.AuditManager;
+import org.apache.syncope.core.persistence.api.DomainsHolder;
 import org.apache.syncope.core.persistence.api.SyncopeLoader;
 import org.apache.syncope.core.persistence.api.dao.LoggerDAO;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.datasource.DataSourceUtils;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -38,6 +49,9 @@ import org.springframework.transaction.annotation.Transactional;
 public class LoggerLoader implements SyncopeLoader {
 
     @Autowired
+    private DomainsHolder domainsHolder;
+
+    @Autowired
     private LoggerDAO loggerDAO;
 
     @Autowired
@@ -51,6 +65,39 @@ public class LoggerLoader implements SyncopeLoader {
     @Transactional
     @Override
     public void load() {
+        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+
+        // 1. Audit table and DataSource for each configured domain
+        ColumnConfig[] columns = {
+            ColumnConfig.createColumnConfig(ctx.getConfiguration(), "EVENT_DATE", null, null, "true", null, null),
+            ColumnConfig.createColumnConfig(ctx.getConfiguration(), "LOGGER_LEVEL", "%level", null, null, null, null),
+            ColumnConfig.createColumnConfig(ctx.getConfiguration(), "LOGGER", "%logger", null, null, null, null),
+            ColumnConfig.createColumnConfig(ctx.getConfiguration(), "MESSAGE", "%message", null, null, null, null),
+            ColumnConfig.createColumnConfig(ctx.getConfiguration(), "THROWABLE", "%ex{full}", null, null, null, null)
+        };
+        for (Map.Entry<String, DataSource> entry : domainsHolder.getDomains().entrySet()) {
+            Appender appender = ctx.getConfiguration().getAppender("audit_for_" + entry.getKey());
+            if (appender == null) {
+                appender = JdbcAppender.createAppender(
+                        "audit_for_" + entry.getKey(),
+                        "false",
+                        null,
+                        new DataSourceConnectionSource(entry.getValue()),
+                        "0",
+                        "SYNCOPEAUDIT",
+                        columns);
+                appender.start();
+                ctx.getConfiguration().addAppender(appender);
+            }
+
+            LoggerConfig logConf = new LoggerConfig(
+                    AuditManager.getDomainAuditLoggerName(entry.getKey()), null, false);
+            logConf.addAppender(appender, Level.DEBUG, null);
+            ctx.getConfiguration().addLogger(AuditManager.getDomainAuditLoggerName(entry.getKey()), logConf);
+        }
+        ctx.updateLoggers();
+
+        // 2. Aligning log4j conf with database content
         Map<String, Logger> syncopeLoggers = new HashMap<>();
         for (Logger syncopeLogger : loggerDAO.findAll(LoggerType.LOG)) {
             syncopeLoggers.put(syncopeLogger.getKey(), syncopeLogger);
@@ -60,8 +107,6 @@ public class LoggerLoader implements SyncopeLoader {
             syncopeLoggers.put(syncopeLogger.getKey(), syncopeLogger);
         }
 
-        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
-
         /*
          * Traverse all defined log4j loggers: if there is a matching SyncopeLogger, set log4j level accordingly,
          * otherwise create a SyncopeLogger instance with given name and level.
@@ -95,4 +140,19 @@ public class LoggerLoader implements SyncopeLoader {
 
         ctx.updateLoggers();
     }
+
+    private static class DataSourceConnectionSource implements ConnectionSource {
+
+        private final DataSource dataSource;
+
+        public DataSourceConnectionSource(final DataSource dataSource) {
+            this.dataSource = dataSource;
+        }
+
+        @Override
+        public Connection getConnection() throws SQLException {
+            return DataSourceUtils.getConnection(dataSource);
+        }
+
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/main/java/org/apache/syncope/core/logic/init/LogicInitializer.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LogicInitializer.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LogicInitializer.java
index 9ae6aa9..856fa13 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LogicInitializer.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LogicInitializer.java
@@ -23,6 +23,7 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.SyncopeLoader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -61,6 +62,8 @@ public class LogicInitializer implements InitializingBean, BeanFactoryAware {
             }
         });
 
+        ApplicationContextProvider.setBeanFactory(beanFactory);
+
         LOG.debug("Starting initialization...");
         for (SyncopeLoader loader : loaders) {
             LOG.debug("Invoking {} with priority {}", AopUtils.getTargetClass(loader).getName(), loader.getPriority());

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJob.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJob.java b/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJob.java
index 9e40ff1..a0f21f3 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJob.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJob.java
@@ -18,21 +18,8 @@
  */
 package org.apache.syncope.core.logic.notification;
 
-import java.util.Date;
-import java.util.Properties;
-import javax.mail.internet.MimeMessage;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.lib.types.AuditElements;
-import org.apache.syncope.common.lib.types.AuditElements.Result;
-import org.apache.syncope.common.lib.types.TaskType;
-import org.apache.syncope.common.lib.types.TraceLevel;
-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.misc.AuditManager;
-import org.apache.syncope.core.misc.ExceptionUtils2;
-import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+import org.apache.syncope.core.misc.security.AuthContextUtils;
+import org.apache.syncope.core.persistence.api.DomainsHolder;
 import org.quartz.DisallowConcurrentExecution;
 import org.quartz.Job;
 import org.quartz.JobExecutionContext;
@@ -40,15 +27,12 @@ import org.quartz.JobExecutionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 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;
 
 /**
  * Periodically checks for notification to send.
  *
- * @see NotificationTask
+ * @see org.apache.syncope.core.persistence.api.entity.task.NotificationTask
  */
 @Component
 @DisallowConcurrentExecution
@@ -63,221 +47,30 @@ public class NotificationJob implements Job {
 
     public static final String DEFAULT_CRON_EXP = "0 0/5 * * * ?";
 
-    /**
-     * Logger.
-     */
     private static final Logger LOG = LoggerFactory.getLogger(NotificationJob.class);
 
     @Autowired
-    private AuditManager auditManager;
+    private DomainsHolder domainsHolder;
 
     @Autowired
-    private NotificationManager notificationManager;
-
-    @Autowired
-    private JavaMailSender mailSender;
-
-    @Autowired
-    private EntityFactory entityFactory;
-
-    /**
-     * Task DAO.
-     */
-    @Autowired
-    private TaskDAO taskDAO;
-
-    private long maxRetries;
-
-    private void init() {
-        maxRetries = notificationManager.getMaxRetries();
-
-        if (mailSender instanceof JavaMailSenderImpl
-                && StringUtils.isNotBlank(((JavaMailSenderImpl) mailSender).getUsername())) {
-
-            Properties javaMailProperties = ((JavaMailSenderImpl) mailSender).getJavaMailProperties();
-            javaMailProperties.setProperty("mail.smtp.auth", "true");
-            ((JavaMailSenderImpl) mailSender).setJavaMailProperties(javaMailProperties);
-        }
-    }
-
-    public TaskExec executeSingle(final NotificationTask task) {
-        init();
-
-        TaskExec execution = entityFactory.newEntity(TaskExec.class);
-        execution.setTask(task);
-        execution.setStartDate(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(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(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());
-                    }
-
-                    auditManager.audit(
-                            AuditElements.EventCategoryType.TASK,
-                            "notification",
-                            null,
-                            "send",
-                            Result.SUCCESS,
-                            null,
-                            null,
-                            task,
-                            "Successfully sent notification to " + to);
-                } catch (Exception e) {
-                    LOG.error("Could not send e-mail", e);
-
-                    execution.setStatus(Status.NOT_SENT.name());
-                    if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
-                        execution.setMessage(ExceptionUtils2.getFullStackTrace(e));
-                    }
-
-                    auditManager.audit(
-                            AuditElements.EventCategoryType.TASK,
-                            "notification",
-                            null,
-                            "send",
-                            Result.FAILURE,
-                            null,
-                            null,
-                            task,
-                            "Could not send notification to " + to, e);
-                }
-
-                execution.setEndDate(new Date());
-            }
-        }
-
-        if (hasToBeRegistered(execution)) {
-            execution = notificationManager.storeExec(execution);
-            if (retryPossible && (Status.valueOf(execution.getStatus()) == Status.NOT_SENT)) {
-                handleRetries(execution);
-            }
-        } else {
-            notificationManager.setTaskExecuted(execution.getTask().getKey(), true);
-        }
-
-        return execution;
-    }
+    private NotificationJobDelegate delegate;
 
     @Override
-    public void execute(final JobExecutionContext context)
-            throws JobExecutionException {
-
+    public void execute(final JobExecutionContext context) throws JobExecutionException {
         LOG.debug("Waking up...");
 
-        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);
+        for (String domain : domainsHolder.getDomains().keySet()) {
+            AuthContextUtils.setFakeAuth(domain);
+            try {
+                delegate.execute();
+            } catch (Exception e) {
+                throw new JobExecutionException(e);
+            } finally {
+                AuthContextUtils.clearFakeAuth();
+            }
         }
 
         LOG.debug("Sleeping again...");
     }
 
-    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 (Status.valueOf(execution.getStatus()) == Status.NOT_SENT
-                && task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
-                || task.getTraceLevel() == TraceLevel.ALL;
-    }
-
-    private void handleRetries(final TaskExec execution) {
-        if (maxRetries <= 0) {
-            return;
-        }
-
-        long failedExecutionsCount = notificationManager.countExecutionsWithStatus(
-                execution.getTask().getKey(), Status.NOT_SENT.name());
-
-        if (failedExecutionsCount <= maxRetries) {
-            LOG.debug("Execution of notification task {} will be retried [{}/{}]",
-                    execution.getTask(), failedExecutionsCount, maxRetries);
-            notificationManager.setTaskExecuted(execution.getTask().getKey(), false);
-
-            auditManager.audit(
-                    AuditElements.EventCategoryType.TASK,
-                    "notification",
-                    null,
-                    "retry",
-                    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",
-                    Result.FAILURE,
-                    null,
-                    null,
-                    execution,
-                    "Giving up retries on notification task " + execution.getTask().getKey());
-        }
-    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJobDelegate.java b/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJobDelegate.java
new file mode 100644
index 0000000..aaebe77
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJobDelegate.java
@@ -0,0 +1,258 @@
+/*
+ * 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.logic.notification;
+
+import java.util.Date;
+import java.util.Properties;
+import javax.mail.internet.MimeMessage;
+import org.apache.commons.lang3.StringUtils;
+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.misc.AuditManager;
+import org.apache.syncope.core.misc.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.notification.NotificationManager;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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 {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationJobDelegate.class);
+
+    /**
+     * Task DAO.
+     */
+    @Autowired
+    private TaskDAO taskDAO;
+
+    @Autowired
+    private JavaMailSender mailSender;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Autowired
+    private AuditManager auditManager;
+
+    @Autowired
+    private NotificationManager notificationManager;
+
+    private long maxRetries;
+
+    private void init() {
+        maxRetries = notificationManager.getMaxRetries();
+
+        if (mailSender instanceof JavaMailSenderImpl
+                && StringUtils.isNotBlank(((JavaMailSenderImpl) mailSender).getUsername())) {
+
+            Properties javaMailProperties = ((JavaMailSenderImpl) mailSender).getJavaMailProperties();
+            javaMailProperties.setProperty("mail.smtp.auth", "true");
+            ((JavaMailSenderImpl) mailSender).setJavaMailProperties(javaMailProperties);
+        }
+    }
+
+    @Transactional
+    public TaskExec executeSingle(final NotificationTask task) {
+        init();
+
+        TaskExec execution = entityFactory.newEntity(TaskExec.class);
+        execution.setTask(task);
+        execution.setStartDate(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());
+                    }
+
+                    auditManager.audit(
+                            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));
+                    }
+
+                    auditManager.audit(
+                            AuditElements.EventCategoryType.TASK,
+                            "notification",
+                            null,
+                            "send",
+                            AuditElements.Result.FAILURE,
+                            null,
+                            null,
+                            task,
+                            "Could not send notification to " + to, e);
+                }
+
+                execution.setEndDate(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 (maxRetries <= 0) {
+            return;
+        }
+
+        long failedExecutionsCount = notificationManager.countExecutionsWithStatus(
+                execution.getTask().getKey(), NotificationJob.Status.NOT_SENT.name());
+
+        if (failedExecutionsCount <= maxRetries) {
+            LOG.debug("Execution of notification task {} will be retried [{}/{}]",
+                    execution.getTask(), failedExecutionsCount, maxRetries);
+            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/6dfedd8f/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJob.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJob.java b/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJob.java
index e07612a..8e5af91 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJob.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJob.java
@@ -18,74 +18,28 @@
  */
 package org.apache.syncope.core.logic.report;
 
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-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.commons.io.IOUtils;
-import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.report.ReportletConf;
-import org.apache.syncope.common.lib.types.ReportExecStatus;
-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.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.logic.ReportLogic;
-import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
-import org.apache.syncope.core.misc.ExceptionUtils2;
+import org.apache.syncope.core.misc.security.AuthContextUtils;
+import org.apache.syncope.core.provisioning.api.job.JobInstanceLoader;
 import org.quartz.DisallowConcurrentExecution;
 import org.quartz.Job;
 import org.quartz.JobExecutionContext;
 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.xml.sax.helpers.AttributesImpl;
 
 /**
  * Quartz job for executing a given report.
  */
-@SuppressWarnings("unchecked")
 @DisallowConcurrentExecution
 public class ReportJob implements Job {
 
     /**
-     * Logger.
-     */
-    private static final Logger LOG = LoggerFactory.getLogger(ReportJob.class);
-
-    /**
-     * Report DAO.
-     */
-    @Autowired
-    private ReportDAO reportDAO;
-
-    /**
-     * Report execution DAO.
-     */
-    @Autowired
-    private ReportExecDAO reportExecDAO;
-
-    @Autowired
-    private ReportLogic dataBinder;
-
-    @Autowired
-    private EntityFactory entityFactory;
-
-    /**
      * Key, set by the caller, for identifying the report to be executed.
      */
     private Long reportKey;
 
+    @Autowired
+    private ReportJobDelegate delegate;
+
     /**
      * Report id setter.
      *
@@ -95,109 +49,15 @@ public class ReportJob implements Job {
         this.reportKey = reportKey;
     }
 
-    @SuppressWarnings("rawtypes")
     @Override
     public void execute(final JobExecutionContext context) throws JobExecutionException {
-        Report report = reportDAO.find(reportKey);
-        if (report == null) {
-            throw new JobExecutionException("Report " + reportKey + " not found");
-        }
-
-        // 1. create execution
-        ReportExec execution = entityFactory.newEntity(ReportExec.class);
-        execution.setStatus(ReportExecStatus.STARTED);
-        execution.setStartDate(new Date());
-        execution.setReport(report);
-        execution = reportExecDAO.save(execution);
-
-        report.addExec(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();
-            handler = tFactory.newTransformerHandler();
-            Transformer serializer = handler.getTransformer();
-            serializer.setOutputProperty(OutputKeys.ENCODING, SyncopeConstants.DEFAULT_ENCODING);
-            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();
+        AuthContextUtils.setFakeAuth(context.getMergedJobDataMap().getString(JobInstanceLoader.DOMAIN));
         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<Reportlet> reportletClass =
-                        dataBinder.findReportletClassHavingConfClass(reportletConf.getClass());
-                if (reportletClass != null) {
-                    Reportlet<ReportletConf> autowired =
-                            (Reportlet<ReportletConf>) ApplicationContextProvider.getBeanFactory().
-                            createBean(reportletClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
-                    autowired.setConf(reportletConf);
-
-                    // invoke reportlet
-                    try {
-                        autowired.extract(handler);
-                    } catch (Exception e) {
-                        execution.setStatus(ReportExecStatus.FAILURE);
-
-                        Throwable t = e instanceof ReportException
-                                ? e.getCause()
-                                : e;
-                        reportExecutionMessage.
-                                append(ExceptionUtils2.getFullStackTrace(t)).
-                                append("\n==================\n");
-                    }
-                }
-            }
-
-            // report footer
-            handler.endElement("", "", ReportXMLConst.ELEMENT_REPORT);
-            handler.endDocument();
-
-            if (!ReportExecStatus.FAILURE.name().equals(execution.getStatus())) {
-                execution.setStatus(ReportExecStatus.SUCCESS);
-            }
+            delegate.execute(reportKey);
         } catch (Exception e) {
-            execution.setStatus(ReportExecStatus.FAILURE);
-            reportExecutionMessage.append(ExceptionUtils2.getFullStackTrace(e));
-
-            throw new JobExecutionException(e, true);
+            throw new JobExecutionException(e);
         } finally {
-            try {
-                zos.closeEntry();
-                IOUtils.closeQuietly(zos);
-                IOUtils.closeQuietly(baos);
-            } catch (IOException e) {
-                LOG.error("While closing StreamResult's backend", e);
-            }
-
-            execution.setExecResult(baos.toByteArray());
-            execution.setMessage(reportExecutionMessage.toString());
-            execution.setEndDate(new Date());
-            reportExecDAO.save(execution);
+            AuthContextUtils.clearFakeAuth();
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJobDelegate.java b/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJobDelegate.java
new file mode 100644
index 0000000..9920c1f
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJobDelegate.java
@@ -0,0 +1,181 @@
+/*
+ * 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.logic.report;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+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.commons.io.IOUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.common.lib.types.ReportExecStatus;
+import org.apache.syncope.core.logic.ReportLogic;
+import org.apache.syncope.core.misc.ExceptionUtils2;
+import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
+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.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 ReportLogic dataBinder;
+
+    @Transactional
+    public void execute(final Long reportKey) throws JobExecutionException {
+        Report report = reportDAO.find(reportKey);
+        if (report == null) {
+            throw new JobExecutionException("Report " + reportKey + " not found");
+        }
+
+        // 1. create execution
+        ReportExec execution = entityFactory.newEntity(ReportExec.class);
+        execution.setStatus(ReportExecStatus.STARTED);
+        execution.setStartDate(new Date());
+        execution.setReport(report);
+        execution = reportExecDAO.save(execution);
+
+        report.addExec(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();
+            handler = tFactory.newTransformerHandler();
+            Transformer serializer = handler.getTransformer();
+            serializer.setOutputProperty(OutputKeys.ENCODING, SyncopeConstants.DEFAULT_ENCODING);
+            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<Reportlet> reportletClass =
+                        dataBinder.findReportletClassHavingConfClass(reportletConf.getClass());
+                if (reportletClass != null) {
+                    @SuppressWarnings("unchecked")
+                    Reportlet<ReportletConf> autowired =
+                            (Reportlet<ReportletConf>) ApplicationContextProvider.getBeanFactory().
+                            createBean(reportletClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+                    autowired.setConf(reportletConf);
+
+                    // invoke reportlet
+                    try {
+                        autowired.extract(handler);
+                    } catch (Exception e) {
+                        execution.setStatus(ReportExecStatus.FAILURE);
+
+                        Throwable t = e instanceof ReportException
+                                ? e.getCause()
+                                : e;
+                        reportExecutionMessage.
+                                append(ExceptionUtils2.getFullStackTrace(t)).
+                                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();
+                IOUtils.closeQuietly(zos);
+                IOUtils.closeQuietly(baos);
+            } catch (IOException e) {
+                LOG.error("While closing StreamResult's backend", e);
+            }
+
+            execution.setExecResult(baos.toByteArray());
+            execution.setMessage(reportExecutionMessage.toString());
+            execution.setEndDate(new Date());
+            reportExecDAO.save(execution);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/test/java/org/apache/syncope/core/logic/AbstractTest.java
----------------------------------------------------------------------
diff --git a/core/logic/src/test/java/org/apache/syncope/core/logic/AbstractTest.java b/core/logic/src/test/java/org/apache/syncope/core/logic/AbstractTest.java
index 161d0c1..d4b0fa4 100644
--- a/core/logic/src/test/java/org/apache/syncope/core/logic/AbstractTest.java
+++ b/core/logic/src/test/java/org/apache/syncope/core/logic/AbstractTest.java
@@ -23,7 +23,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.transaction.annotation.Transactional;
+import org.springframework.test.context.transaction.TransactionConfiguration;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(locations = {
@@ -33,7 +33,7 @@ import org.springframework.transaction.annotation.Transactional;
     "classpath:persistenceTest.xml",
     "classpath:logicTest.xml"
 })
-@Transactional
+@TransactionConfiguration(transactionManager = "MasterTransactionManager")
 public abstract class AbstractTest {
 
     protected static final Logger LOG = LoggerFactory.getLogger(AbstractTest.class);

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/test/java/org/apache/syncope/core/logic/MappingTest.java
----------------------------------------------------------------------
diff --git a/core/logic/src/test/java/org/apache/syncope/core/logic/MappingTest.java b/core/logic/src/test/java/org/apache/syncope/core/logic/MappingTest.java
index 1f1dab7..770bac9 100644
--- a/core/logic/src/test/java/org/apache/syncope/core/logic/MappingTest.java
+++ b/core/logic/src/test/java/org/apache/syncope/core/logic/MappingTest.java
@@ -31,7 +31,9 @@ import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.identityconnectors.framework.common.objects.Name;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
 
+@Transactional
 public class MappingTest extends AbstractTest {
 
     @Autowired

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/test/java/org/apache/syncope/core/logic/NotificationTest.java
----------------------------------------------------------------------
diff --git a/core/logic/src/test/java/org/apache/syncope/core/logic/NotificationTest.java b/core/logic/src/test/java/org/apache/syncope/core/logic/NotificationTest.java
index c5dc13c..6c208a2 100644
--- a/core/logic/src/test/java/org/apache/syncope/core/logic/NotificationTest.java
+++ b/core/logic/src/test/java/org/apache/syncope/core/logic/NotificationTest.java
@@ -79,7 +79,9 @@ import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.transaction.annotation.Transactional;
 
+@Transactional
 public class NotificationTest extends AbstractTest {
 
     private static final String SMTP_HOST = "localhost";
@@ -164,7 +166,7 @@ public class NotificationTest extends AbstractTest {
     }
 
     private static UserTO getSampleTO(final String email) {
-        String uid = email;
+        String uid = UUID.randomUUID().toString().substring(0, 8) + email;
         UserTO userTO = new UserTO();
         userTO.setPassword("password123");
         userTO.setUsername(uid);
@@ -175,7 +177,7 @@ public class NotificationTest extends AbstractTest {
         userTO.getPlainAttrs().add(attributeTO("surname", "surname"));
         userTO.getPlainAttrs().add(attributeTO("type", "a type"));
         userTO.getPlainAttrs().add(attributeTO("userId", uid));
-        userTO.getPlainAttrs().add(attributeTO("email", uid));
+        userTO.getPlainAttrs().add(attributeTO("email", email));
         userTO.getPlainAttrs().add(attributeTO("loginDate", new SimpleDateFormat("yyyy-MM-dd").format(new Date())));
         userTO.getDerAttrs().add(attributeTO("cn", null));
         userTO.getVirAttrs().add(attributeTO("virtualdata", "virtualvalue"));

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/logic/src/test/resources/logicTest.xml
----------------------------------------------------------------------
diff --git a/core/logic/src/test/resources/logicTest.xml b/core/logic/src/test/resources/logicTest.xml
index 3cf0ab0..2dca918 100644
--- a/core/logic/src/test/resources/logicTest.xml
+++ b/core/logic/src/test/resources/logicTest.xml
@@ -26,6 +26,7 @@ under the License.
     <property name="locations">
       <list>
         <value>classpath:persistence.properties</value>
+        <value>classpath:domains/*.properties</value>
         <value>classpath:security.properties</value>
         <value>classpath:connid.properties</value>
         <value>classpath:mail.properties</value>
@@ -37,10 +38,5 @@ under the License.
     <property name="ignoreResourceNotFound" value="true"/>
     <property name="ignoreUnresolvablePlaceholders" value="true"/>
   </bean>
-  
-  <bean id="contentXML" class="org.apache.syncope.core.misc.spring.ResourceWithFallbackLoader">
-    <property name="primary" value="file:${conf.directory}/content.xml"/>
-    <property name="fallback" value="classpath:content.xml"/>
-  </bean>
 
 </beans>

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/misc/src/main/java/org/apache/syncope/core/misc/AuditManager.java
----------------------------------------------------------------------
diff --git a/core/misc/src/main/java/org/apache/syncope/core/misc/AuditManager.java b/core/misc/src/main/java/org/apache/syncope/core/misc/AuditManager.java
index a8d8aac..d370895 100644
--- a/core/misc/src/main/java/org/apache/syncope/core/misc/AuditManager.java
+++ b/core/misc/src/main/java/org/apache/syncope/core/misc/AuditManager.java
@@ -23,13 +23,18 @@ import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditElements.Result;
 import org.apache.syncope.common.lib.types.AuditLoggerName;
 import org.apache.syncope.common.lib.types.LoggerLevel;
+import org.apache.syncope.common.lib.types.LoggerType;
+import org.apache.syncope.core.misc.security.AuthContextUtils;
+import org.apache.syncope.core.misc.security.SyncopeAuthenticationDetails;
 import org.apache.syncope.core.persistence.api.dao.LoggerDAO;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class AuditManager {
@@ -39,6 +44,11 @@ public class AuditManager {
     @Autowired
     private LoggerDAO loggerDAO;
 
+    public static String getDomainAuditLoggerName(final String domain) {
+        return LoggerType.AUDIT.getPrefix() + "." + domain;
+    }
+
+    @Transactional(readOnly = true)
     public void audit(
             final AuditElements.EventCategoryType type,
             final String category,
@@ -88,13 +98,22 @@ public class AuditManager {
             if (syncopeLogger != null && syncopeLogger.getLevel() == LoggerLevel.DEBUG) {
                 StringBuilder auditMessage = new StringBuilder();
 
-                final SecurityContext ctx = SecurityContextHolder.getContext();
+                SecurityContext ctx = SecurityContextHolder.getContext();
                 if (ctx != null && ctx.getAuthentication() != null) {
                     auditMessage.append('[').append(ctx.getAuthentication().getName()).append("] ");
                 }
                 auditMessage.append(message);
 
-                final Logger logger = LoggerFactory.getLogger(auditLoggerName.toLoggerName());
+                String domain = AuthContextUtils.getDomain();
+                if (input != null && input.length > 0 && input[0] instanceof UsernamePasswordAuthenticationToken) {
+                    UsernamePasswordAuthenticationToken token =
+                            UsernamePasswordAuthenticationToken.class.cast(input[0]);
+                    if (token.getDetails() instanceof SyncopeAuthenticationDetails) {
+                        domain = SyncopeAuthenticationDetails.class.cast(token.getDetails()).getDomain();
+                    }
+                }
+
+                Logger logger = LoggerFactory.getLogger(getDomainAuditLoggerName(domain));
                 if (throwable == null) {
                     logger.debug(auditMessage.toString());
                 } else {

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtils.java
----------------------------------------------------------------------
diff --git a/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtils.java b/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtils.java
index e655809..95a16b1 100644
--- a/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtils.java
+++ b/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtils.java
@@ -73,7 +73,7 @@ import org.identityconnectors.framework.common.objects.Name;
 import org.identityconnectors.framework.common.objects.OperationalAttributes;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
 
 public final class MappingUtils {
 
@@ -141,9 +141,9 @@ public final class MappingUtils {
         LOG.debug("Preparing resource attributes for {} with provision {} for attributes {}",
                 any, provision, any.getPlainAttrs());
 
-        ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext();
-        VirAttrCache virAttrCache = context.getBean(VirAttrCache.class);
-        PasswordGenerator passwordGenerator = context.getBean(PasswordGenerator.class);
+        DefaultListableBeanFactory beanFactory = ApplicationContextProvider.getBeanFactory();
+        VirAttrCache virAttrCache = beanFactory.getBean(VirAttrCache.class);
+        PasswordGenerator passwordGenerator = beanFactory.getBean(PasswordGenerator.class);
 
         Set<Attribute> attributes = new HashSet<>();
         String connObjectKey = null;
@@ -227,9 +227,9 @@ public final class MappingUtils {
 
         List<Any<?, ?, ?>> anys = new ArrayList<>();
 
-        ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext();
-        AnyUtilsFactory anyUtilsFactory = context.getBean(AnyUtilsFactory.class);
-        VirAttrHandler virAttrHandler = context.getBean(VirAttrHandler.class);
+        DefaultListableBeanFactory beanFactory = ApplicationContextProvider.getBeanFactory();
+        AnyUtilsFactory anyUtilsFactory = beanFactory.getBean(AnyUtilsFactory.class);
+        VirAttrHandler virAttrHandler = beanFactory.getBean(VirAttrHandler.class);
 
         switch (mapItem.getIntMappingType().getAnyTypeKind()) {
             case USER:
@@ -240,7 +240,7 @@ public final class MappingUtils {
 
             case GROUP:
                 if (any instanceof User) {
-                    UserDAO userDAO = context.getBean(UserDAO.class);
+                    UserDAO userDAO = beanFactory.getBean(UserDAO.class);
                     for (Group group : userDAO.findAllGroups((User) any)) {
                         virAttrHandler.retrieveVirAttrValues(group);
                         anys.add(group);
@@ -271,7 +271,7 @@ public final class MappingUtils {
             case UserPlainSchema:
             case GroupPlainSchema:
             case AnyObjectPlainSchema:
-                final PlainSchemaDAO plainSchemaDAO = context.getBean(PlainSchemaDAO.class);
+                PlainSchemaDAO plainSchemaDAO = beanFactory.getBean(PlainSchemaDAO.class);
                 schema = plainSchemaDAO.find(mapItem.getIntAttrName());
                 schemaType = schema == null ? AttrSchemaType.String : schema.getType();
                 break;
@@ -279,7 +279,7 @@ public final class MappingUtils {
             case UserVirtualSchema:
             case GroupVirtualSchema:
             case AnyObjectVirtualSchema:
-                VirSchemaDAO virSchemaDAO = context.getBean(VirSchemaDAO.class);
+                VirSchemaDAO virSchemaDAO = beanFactory.getBean(VirSchemaDAO.class);
                 VirSchema virSchema = virSchemaDAO.find(mapItem.getIntAttrName());
                 readOnlyVirSchema = (virSchema != null && virSchema.isReadonly());
                 schemaType = AttrSchemaType.String;
@@ -434,9 +434,9 @@ public final class MappingUtils {
         LOG.debug("Get attributes for '{}' and mapping type '{}'", anys, mappingItem.getIntMappingType());
 
         EntityFactory entityFactory =
-                ApplicationContextProvider.getApplicationContext().getBean(EntityFactory.class);
+                ApplicationContextProvider.getBeanFactory().getBean(EntityFactory.class);
         AnyUtilsFactory anyUtilsFactory =
-                ApplicationContextProvider.getApplicationContext().getBean(AnyUtilsFactory.class);
+                ApplicationContextProvider.getBeanFactory().getBean(AnyUtilsFactory.class);
         List<PlainAttrValue> values = new ArrayList<>();
         PlainAttrValue attrValue;
         switch (mappingItem.getIntMappingType()) {
@@ -551,7 +551,7 @@ public final class MappingUtils {
                 break;
 
             case GroupOwnerSchema:
-                AnyTypeDAO anyTypeDAO = ApplicationContextProvider.getApplicationContext().getBean(AnyTypeDAO.class);
+                AnyTypeDAO anyTypeDAO = ApplicationContextProvider.getBeanFactory().getBean(AnyTypeDAO.class);
                 Mapping uMapping = provision.getAnyType().equals(anyTypeDAO.findUser())
                         ? null
                         : provision.getMapping();

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyEnforcer.java b/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyEnforcer.java
index 4b8faec..13ddc84 100644
--- a/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyEnforcer.java
+++ b/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyEnforcer.java
@@ -22,8 +22,6 @@ import java.util.regex.Pattern;
 import org.apache.syncope.common.lib.types.AccountPolicySpec;
 import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.core.persistence.api.entity.user.User;
-import org.apache.syncope.core.provisioning.api.UserSuspender;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -31,11 +29,8 @@ public class AccountPolicyEnforcer implements PolicyEnforcer<AccountPolicySpec,
 
     private static final Pattern DEFAULT_PATTERN = Pattern.compile("[a-zA-Z0-9-_@. ]+");
 
-    @Autowired(required = false)
-    private UserSuspender userSuspender;
-
     @Override
-    public void enforce(final AccountPolicySpec policy, final PolicyType type, final User user) {
+    public boolean enforce(final AccountPolicySpec policy, final PolicyType type, final User user) {
         if (user.getUsername() == null) {
             throw new PolicyEnforceException("Invalid account");
         }
@@ -90,11 +85,7 @@ public class AccountPolicyEnforcer implements PolicyEnforcer<AccountPolicySpec,
         }
 
         // check for subsequent failed logins
-        if (userSuspender != null
-                && user.getFailedLogins() != null && policy.getMaxAuthenticationAttempts() > 0
-                && user.getFailedLogins() > policy.getMaxAuthenticationAttempts() && !user.isSuspended()) {
-
-            userSuspender.suspend(user, policy.isPropagateSuspension());
-        }
+        return (user.getFailedLogins() != null && policy.getMaxAuthenticationAttempts() > 0
+                && user.getFailedLogins() > policy.getMaxAuthenticationAttempts() && !user.isSuspended());
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6dfedd8f/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyEnforcer.java b/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyEnforcer.java
index 41a6bb6..1313d2e 100644
--- a/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyEnforcer.java
+++ b/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyEnforcer.java
@@ -27,7 +27,7 @@ import org.springframework.stereotype.Component;
 public class PasswordPolicyEnforcer implements PolicyEnforcer<PasswordPolicySpec, User> {
 
     @Override
-    public void enforce(final PasswordPolicySpec policy, final PolicyType type, final User user) {
+    public boolean enforce(final PasswordPolicySpec policy, final PolicyType type, final User user) {
         final String clearPassword = user.getClearPassword();
         final String password = user.getPassword();
 
@@ -147,6 +147,8 @@ public class PasswordPolicyEnforcer implements PolicyEnforcer<PasswordPolicySpec
                 throw new PasswordPolicyException("Password mustn't end with a non-alphanumeric character");
             }
         }
+
+        return false;
     }
 
     private boolean checkForDigit(final String str) {