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 2015/01/08 14:17:29 UTC

[09/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/PolicyController.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/PolicyController.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/PolicyController.java
new file mode 100644
index 0000000..fbfb127
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/PolicyController.java
@@ -0,0 +1,197 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.to.AbstractPolicyTO;
+import org.apache.syncope.common.lib.to.AccountPolicyTO;
+import org.apache.syncope.common.lib.to.PasswordPolicyTO;
+import org.apache.syncope.common.lib.to.SyncPolicyTO;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.persistence.api.entity.AccountPolicy;
+import org.apache.syncope.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.persistence.api.entity.Policy;
+import org.apache.syncope.persistence.api.entity.SyncPolicy;
+import org.apache.syncope.server.logic.data.PolicyDataBinder;
+import org.apache.syncope.server.logic.init.ImplementationClassNamesLoader;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PolicyController extends AbstractTransactionalLogic<AbstractPolicyTO> {
+
+    @Autowired
+    private ImplementationClassNamesLoader classNamesLoader;
+
+    @Autowired
+    private PolicyDAO policyDAO;
+
+    @Autowired
+    private PolicyDataBinder binder;
+
+    @PreAuthorize("hasRole('POLICY_CREATE')")
+    public <T extends AbstractPolicyTO> T create(final T policyTO) {
+        return binder.getPolicyTO(policyDAO.save(binder.getPolicy(null, policyTO)));
+    }
+
+    private <T extends AbstractPolicyTO, K extends Policy> T update(final T policyTO, final K policy) {
+        binder.getPolicy(policy, policyTO);
+        K savedPolicy = policyDAO.save(policy);
+        return binder.getPolicyTO(savedPolicy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_UPDATE')")
+    public PasswordPolicyTO update(final PasswordPolicyTO policyTO) {
+        Policy policy = policyDAO.find(policyTO.getId());
+        if (!(policy instanceof PasswordPolicy)) {
+            throw new NotFoundException("PasswordPolicy with id " + policyTO.getId());
+        }
+
+        return update(policyTO, policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_UPDATE')")
+    public AccountPolicyTO update(final AccountPolicyTO policyTO) {
+        Policy policy = policyDAO.find(policyTO.getId());
+        if (!(policy instanceof AccountPolicy)) {
+            throw new NotFoundException("AccountPolicy with id " + policyTO.getId());
+        }
+
+        return update(policyTO, policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_UPDATE')")
+    public SyncPolicyTO update(final SyncPolicyTO policyTO) {
+        Policy policy = policyDAO.find(policyTO.getId());
+        if (!(policy instanceof SyncPolicy)) {
+            throw new NotFoundException("SyncPolicy with id " + policyTO.getId());
+        }
+
+        return update(policyTO, policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_LIST')")
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractPolicyTO> List<T> list(final PolicyType type) {
+
+        List<? extends Policy> policies = policyDAO.find(type);
+
+        final List<T> policyTOs = new ArrayList<T>();
+        for (Policy policy : policies) {
+            policyTOs.add((T) binder.getPolicyTO(policy));
+        }
+
+        return policyTOs;
+    }
+
+    @PreAuthorize("hasRole('POLICY_READ')")
+    public PasswordPolicyTO getGlobalPasswordPolicy() {
+        PasswordPolicy policy = policyDAO.getGlobalPasswordPolicy();
+        if (policy == null) {
+            throw new NotFoundException("No password policy found");
+        }
+
+        return (PasswordPolicyTO) binder.getPolicyTO(policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_READ')")
+    public AccountPolicyTO getGlobalAccountPolicy() {
+        AccountPolicy policy = policyDAO.getGlobalAccountPolicy();
+        if (policy == null) {
+            throw new NotFoundException("No account policy found");
+        }
+
+        return (AccountPolicyTO) binder.getPolicyTO(policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_READ')")
+    public SyncPolicyTO getGlobalSyncPolicy() {
+        SyncPolicy policy = policyDAO.getGlobalSyncPolicy();
+        if (policy == null) {
+            throw new NotFoundException("No sync policy found");
+        }
+
+        return (SyncPolicyTO) binder.getPolicyTO(policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_READ')")
+    public <T extends AbstractPolicyTO> T read(final Long id) {
+        Policy policy = policyDAO.find(id);
+        if (policy == null) {
+            throw new NotFoundException("Policy " + id + " not found");
+        }
+
+        return binder.getPolicyTO(policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_DELETE')")
+    public <T extends AbstractPolicyTO> T delete(final Long id) {
+        Policy policy = policyDAO.find(id);
+        if (policy == null) {
+            throw new NotFoundException("Policy " + id + " not found");
+        }
+
+        T policyToDelete = binder.getPolicyTO(policy);
+        policyDAO.delete(policy);
+
+        return policyToDelete;
+    }
+
+    @PreAuthorize("hasRole('POLICY_LIST')")
+    public Set<String> getSyncCorrelationRuleClasses() {
+        return classNamesLoader.getClassNames(ImplementationClassNamesLoader.Type.SYNC_CORRELATION_RULES);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected AbstractPolicyTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof AbstractPolicyTO) {
+                    id = ((AbstractPolicyTO) args[i]).getId();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return binder.getPolicyTO(policyDAO.find(id));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ReportLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ReportLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ReportLogic.java
new file mode 100644
index 0000000..0d8af57
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ReportLogic.java
@@ -0,0 +1,348 @@
+/*
+ * 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.server.logic;
+
+import java.io.ByteArrayInputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipInputStream;
+import org.apache.cocoon.optional.pipeline.components.sax.fop.FopSerializer;
+import org.apache.cocoon.pipeline.NonCachingPipeline;
+import org.apache.cocoon.pipeline.Pipeline;
+import org.apache.cocoon.sax.SAXPipelineComponent;
+import org.apache.cocoon.sax.component.XMLGenerator;
+import org.apache.cocoon.sax.component.XMLSerializer;
+import org.apache.cocoon.sax.component.XSLTTransformer;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.common.lib.to.ReportExecTO;
+import org.apache.syncope.common.lib.to.ReportTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.ReportExecExportFormat;
+import org.apache.syncope.common.lib.types.ReportExecStatus;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.ReportDAO;
+import org.apache.syncope.persistence.api.dao.ReportExecDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.Report;
+import org.apache.syncope.persistence.api.entity.ReportExec;
+import org.apache.syncope.server.logic.data.ReportDataBinder;
+import org.apache.syncope.server.logic.init.JobInstanceLoader;
+import org.apache.syncope.server.logic.report.Reportlet;
+import org.apache.syncope.server.logic.report.TextSerializer;
+import org.apache.xmlgraphics.util.MimeConstants;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class ReportLogic extends AbstractTransactionalLogic<ReportTO> {
+
+    @Autowired
+    private ReportDAO reportDAO;
+
+    @Autowired
+    private ReportExecDAO reportExecDAO;
+
+    @Autowired
+    private JobInstanceLoader jobInstanceLoader;
+
+    @Autowired
+    private SchedulerFactoryBean scheduler;
+
+    @Autowired
+    private ReportDataBinder binder;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @PreAuthorize("hasRole('REPORT_CREATE')")
+    public ReportTO create(final ReportTO reportTO) {
+        Report report = entityFactory.newEntity(Report.class);
+        binder.getReport(report, reportTO);
+        report = reportDAO.save(report);
+
+        try {
+            jobInstanceLoader.registerJob(report);
+        } catch (Exception e) {
+            LOG.error("While registering quartz job for report " + report.getKey(), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+
+        return binder.getReportTO(report);
+    }
+
+    @PreAuthorize("hasRole('REPORT_UPDATE')")
+    public ReportTO update(final ReportTO reportTO) {
+        Report report = reportDAO.find(reportTO.getId());
+        if (report == null) {
+            throw new NotFoundException("Report " + reportTO.getId());
+        }
+
+        binder.getReport(report, reportTO);
+        report = reportDAO.save(report);
+
+        try {
+            jobInstanceLoader.registerJob(report);
+        } catch (Exception e) {
+            LOG.error("While registering quartz job for report " + report.getKey(), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+
+        return binder.getReportTO(report);
+    }
+
+    @PreAuthorize("hasRole('REPORT_LIST')")
+    public int count() {
+        return reportDAO.count();
+    }
+
+    @PreAuthorize("hasRole('REPORT_LIST')")
+    public List<ReportTO> list(final int page, final int size, final List<OrderByClause> orderByClauses) {
+        List<Report> reports = reportDAO.findAll(page, size, orderByClauses);
+        List<ReportTO> result = new ArrayList<ReportTO>(reports.size());
+        for (Report report : reports) {
+            result.add(binder.getReportTO(report));
+        }
+        return result;
+    }
+
+    @PreAuthorize("hasRole('REPORT_LIST')")
+    @SuppressWarnings("rawtypes")
+    public Set<String> getReportletConfClasses() {
+        Set<String> reportletConfClasses = new HashSet<String>();
+
+        for (Class<Reportlet> reportletClass : binder.getAllReportletClasses()) {
+            Class<? extends ReportletConf> reportletConfClass = binder.getReportletConfClass(reportletClass);
+            if (reportletConfClass != null) {
+                reportletConfClasses.add(reportletConfClass.getName());
+            }
+        }
+
+        return reportletConfClasses;
+    }
+
+    @PreAuthorize("hasRole('REPORT_READ')")
+    public ReportTO read(final Long reportId) {
+        Report report = reportDAO.find(reportId);
+        if (report == null) {
+            throw new NotFoundException("Report " + reportId);
+        }
+        return binder.getReportTO(report);
+    }
+
+    @PreAuthorize("hasRole('REPORT_READ')")
+    @Transactional(readOnly = true)
+    public ReportExecTO readExecution(final Long executionId) {
+        ReportExec reportExec = reportExecDAO.find(executionId);
+        if (reportExec == null) {
+            throw new NotFoundException("Report execution " + executionId);
+        }
+        return binder.getReportExecTO(reportExec);
+    }
+
+    @PreAuthorize("hasRole('REPORT_READ')")
+    public void exportExecutionResult(final OutputStream os, final ReportExec reportExec,
+            final ReportExecExportFormat format) {
+
+        // streaming SAX handler from a compressed byte array stream
+        ByteArrayInputStream bais = new ByteArrayInputStream(reportExec.getExecResult());
+        ZipInputStream zis = new ZipInputStream(bais);
+        try {
+            // a single ZipEntry in the ZipInputStream (see ReportJob)
+            zis.getNextEntry();
+
+            Pipeline<SAXPipelineComponent> pipeline = new NonCachingPipeline<SAXPipelineComponent>();
+            pipeline.addComponent(new XMLGenerator(zis));
+
+            Map<String, Object> parameters = new HashMap<String, Object>();
+            parameters.put("status", reportExec.getStatus());
+            parameters.put("message", reportExec.getMessage());
+            parameters.put("startDate", reportExec.getStartDate());
+            parameters.put("endDate", reportExec.getEndDate());
+
+            switch (format) {
+                case HTML:
+                    XSLTTransformer xsl2html = new XSLTTransformer(getClass().getResource("/report/report2html.xsl"));
+                    xsl2html.setParameters(parameters);
+                    pipeline.addComponent(xsl2html);
+                    pipeline.addComponent(XMLSerializer.createXHTMLSerializer());
+                    break;
+
+                case PDF:
+                    XSLTTransformer xsl2pdf = new XSLTTransformer(getClass().getResource("/report/report2fo.xsl"));
+                    xsl2pdf.setParameters(parameters);
+                    pipeline.addComponent(xsl2pdf);
+                    pipeline.addComponent(new FopSerializer(MimeConstants.MIME_PDF));
+                    break;
+
+                case RTF:
+                    XSLTTransformer xsl2rtf = new XSLTTransformer(getClass().getResource("/report/report2fo.xsl"));
+                    xsl2rtf.setParameters(parameters);
+                    pipeline.addComponent(xsl2rtf);
+                    pipeline.addComponent(new FopSerializer(MimeConstants.MIME_RTF));
+                    break;
+
+                case CSV:
+                    XSLTTransformer xsl2csv = new XSLTTransformer(getClass().getResource("/report/report2csv.xsl"));
+                    xsl2csv.setParameters(parameters);
+                    pipeline.addComponent(xsl2csv);
+                    pipeline.addComponent(new TextSerializer());
+                    break;
+
+                case XML:
+                default:
+                    pipeline.addComponent(XMLSerializer.createXMLSerializer());
+            }
+
+            pipeline.setup(os);
+            pipeline.execute();
+
+            LOG.debug("Result of {} successfully exported as {}", reportExec, format);
+        } catch (Exception e) {
+            LOG.error("While exporting content", e);
+        } finally {
+            IOUtils.closeQuietly(zis);
+            IOUtils.closeQuietly(bais);
+        }
+    }
+
+    @PreAuthorize("hasRole('REPORT_READ')")
+    public ReportExec getAndCheckReportExec(final Long executionId) {
+        ReportExec reportExec = reportExecDAO.find(executionId);
+        if (reportExec == null) {
+            throw new NotFoundException("Report execution " + executionId);
+        }
+        if (!ReportExecStatus.SUCCESS.name().equals(reportExec.getStatus()) || reportExec.getExecResult() == null) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidReportExec);
+            sce.getElements().add(reportExec.getExecResult() == null
+                    ? "No report data produced"
+                    : "Report did not run successfully");
+            throw sce;
+        }
+        return reportExec;
+    }
+
+    @PreAuthorize("hasRole('REPORT_EXECUTE')")
+    public ReportExecTO execute(final Long reportId) {
+        Report report = reportDAO.find(reportId);
+        if (report == null) {
+            throw new NotFoundException("Report " + reportId);
+        }
+
+        try {
+            jobInstanceLoader.registerJob(report);
+
+            scheduler.getScheduler().triggerJob(
+                    new JobKey(JobInstanceLoader.getJobName(report), Scheduler.DEFAULT_GROUP));
+        } catch (Exception e) {
+            LOG.error("While executing report {}", report, e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+
+        ReportExecTO result = new ReportExecTO();
+        result.setReport(reportId);
+        result.setStartDate(new Date());
+        result.setStatus(ReportExecStatus.STARTED.name());
+        result.setMessage("Job fired; waiting for results...");
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('REPORT_DELETE')")
+    public ReportTO delete(final Long reportId) {
+        Report report = reportDAO.find(reportId);
+        if (report == null) {
+            throw new NotFoundException("Report " + reportId);
+        }
+
+        ReportTO deletedReport = binder.getReportTO(report);
+        jobInstanceLoader.unregisterJob(report);
+        reportDAO.delete(report);
+        return deletedReport;
+    }
+
+    @PreAuthorize("hasRole('REPORT_DELETE')")
+    public ReportExecTO deleteExecution(final Long executionId) {
+        ReportExec reportExec = reportExecDAO.find(executionId);
+        if (reportExec == null) {
+            throw new NotFoundException("Report execution " + executionId);
+        }
+
+        ReportExecTO reportExecToDelete = binder.getReportExecTO(reportExec);
+        reportExecDAO.delete(reportExec);
+        return reportExecToDelete;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected ReportTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args) && ("create".equals(method.getName())
+                || "update".equals(method.getName())
+                || "delete".equals(method.getName()))) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof ReportTO) {
+                    id = ((ReportTO) args[i]).getId();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return binder.getReportTO(reportDAO.find(id));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ResourceLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ResourceLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ResourceLogic.java
new file mode 100644
index 0000000..d1b2851
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ResourceLogic.java
@@ -0,0 +1,301 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.BulkAction;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.ConnObjectTO;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.persistence.api.dao.ConnInstanceDAO;
+import org.apache.syncope.persistence.api.dao.DuplicateException;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.persistence.api.entity.ConnInstance;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.provisioning.api.Connector;
+import org.apache.syncope.provisioning.api.ConnectorFactory;
+import org.apache.syncope.server.logic.data.ResourceDataBinder;
+import org.apache.syncope.server.logic.init.ImplementationClassNamesLoader;
+import org.apache.syncope.server.utils.ConnObjectUtil;
+import org.apache.syncope.server.utils.MappingUtil;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeUtil;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.Name;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.Uid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private ConnInstanceDAO connInstanceDAO;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private RoleDAO roleDAO;
+
+    @Autowired
+    private ResourceDataBinder binder;
+
+    @Autowired
+    private ImplementationClassNamesLoader classNamesLoader;
+
+    @Autowired
+    private ConnObjectUtil connObjectUtil;
+
+    @Autowired
+    private ConnectorFactory connFactory;
+
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
+    @PreAuthorize("hasRole('RESOURCE_CREATE')")
+    public ResourceTO create(final ResourceTO resourceTO) {
+        if (StringUtils.isBlank(resourceTO.getKey())) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
+            sce.getElements().add("Resource name");
+            throw sce;
+        }
+
+        if (resourceDAO.find(resourceTO.getKey()) != null) {
+            throw new DuplicateException("Resource '" + resourceTO.getKey() + "'");
+        }
+
+        ExternalResource resource = null;
+        try {
+            resource = resourceDAO.save(binder.create(resourceTO));
+        } catch (SyncopeClientException e) {
+            throw e;
+        } catch (Exception e) {
+            SyncopeClientException ex = SyncopeClientException.build(ClientExceptionType.InvalidExternalResource);
+            ex.getElements().add(e.getMessage());
+            throw ex;
+        }
+
+        return binder.getResourceTO(resource);
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_UPDATE')")
+    public ResourceTO update(final ResourceTO resourceTO) {
+        ExternalResource resource = resourceDAO.find(resourceTO.getKey());
+        if (resource == null) {
+            throw new NotFoundException("Resource '" + resourceTO.getKey() + "'");
+        }
+
+        resource = binder.update(resource, resourceTO);
+        try {
+            resource = resourceDAO.save(resource);
+        } catch (SyncopeClientException e) {
+            throw e;
+        } catch (Exception e) {
+            SyncopeClientException ex = SyncopeClientException.build(ClientExceptionType.InvalidExternalResource);
+            ex.getElements().add(e.getMessage());
+            throw ex;
+        }
+
+        return binder.getResourceTO(resource);
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_DELETE')")
+    public ResourceTO delete(final String resourceName) {
+        ExternalResource resource = resourceDAO.find(resourceName);
+        if (resource == null) {
+            throw new NotFoundException("Resource '" + resourceName + "'");
+        }
+
+        ResourceTO resourceToDelete = binder.getResourceTO(resource);
+
+        resourceDAO.delete(resourceName);
+
+        return resourceToDelete;
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_READ')")
+    @Transactional(readOnly = true)
+    public ResourceTO read(final String resourceName) {
+        ExternalResource resource = resourceDAO.find(resourceName);
+        if (resource == null) {
+            throw new NotFoundException("Resource '" + resourceName + "'");
+        }
+
+        return binder.getResourceTO(resource);
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_READ')")
+    public Set<String> getPropagationActionsClasses() {
+        Set<String> actionsClasses = classNamesLoader.getClassNames(
+                ImplementationClassNamesLoader.Type.PROPAGATION_ACTIONS);
+
+        return actionsClasses;
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
+    public List<ResourceTO> list(final Long connInstanceId) {
+        List<? extends ExternalResource> resources;
+
+        if (connInstanceId == null) {
+            resources = resourceDAO.findAll();
+        } else {
+            ConnInstance connInstance = connInstanceDAO.find(connInstanceId);
+            resources = connInstance.getResources();
+        }
+
+        return binder.getResourceTOs(resources);
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_GETCONNECTOROBJECT')")
+    @Transactional(readOnly = true)
+    public ConnObjectTO getConnectorObject(final String resourceName, final SubjectType type, final Long id) {
+        ExternalResource resource = resourceDAO.find(resourceName);
+        if (resource == null) {
+            throw new NotFoundException("Resource '" + resourceName + "'");
+        }
+
+        Subject<?, ?, ?> subject = type == SubjectType.USER
+                ? userDAO.find(id)
+                : roleDAO.find(id);
+        if (subject == null) {
+            throw new NotFoundException(type + " " + id);
+        }
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(type.asAttributableType());
+
+        MappingItem accountIdItem = attrUtil.getAccountIdItem(resource);
+        if (accountIdItem == null) {
+            throw new NotFoundException("AccountId mapping for " + type + " " + id + " on resource '" + resourceName
+                    + "'");
+        }
+        final String accountIdValue = MappingUtil.getAccountIdValue(
+                subject, resource, attrUtil.getAccountIdItem(resource));
+
+        final ObjectClass objectClass = SubjectType.USER == type ? ObjectClass.ACCOUNT : ObjectClass.GROUP;
+
+        final Connector connector = connFactory.getConnector(resource);
+        final ConnectorObject connectorObject = connector.getObject(objectClass, new Uid(accountIdValue),
+                connector.getOperationOptions(attrUtil.getMappingItems(resource, MappingPurpose.BOTH)));
+        if (connectorObject == null) {
+            throw new NotFoundException("Object " + accountIdValue + " with class " + objectClass
+                    + "not found on resource " + resourceName);
+        }
+
+        final Set<Attribute> attributes = connectorObject.getAttributes();
+        if (AttributeUtil.find(Uid.NAME, attributes) == null) {
+            attributes.add(connectorObject.getUid());
+        }
+        if (AttributeUtil.find(Name.NAME, attributes) == null) {
+            attributes.add(connectorObject.getName());
+        }
+
+        return connObjectUtil.getConnObjectTO(connectorObject);
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_READ')")
+    @Transactional(readOnly = true)
+    public boolean check(final ResourceTO resourceTO) {
+        final ConnInstance connInstance = binder.getConnInstance(resourceTO);
+
+        final Connector connector = connFactory.createConnector(connInstance, connInstance.getConfiguration());
+
+        boolean result;
+        try {
+            connector.test();
+            result = true;
+        } catch (Exception e) {
+            LOG.error("Test connection failure {}", e);
+            result = false;
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_DELETE') and #bulkAction.operation == #bulkAction.operation.DELETE")
+    public BulkActionResult bulk(final BulkAction bulkAction) {
+        BulkActionResult res = new BulkActionResult();
+
+        if (bulkAction.getOperation() == BulkAction.Type.DELETE) {
+            for (String name : bulkAction.getTargets()) {
+                try {
+                    res.add(delete(name).getKey(), BulkActionResult.Status.SUCCESS);
+                } catch (Exception e) {
+                    LOG.error("Error performing delete for resource {}", name, e);
+                    res.add(name, BulkActionResult.Status.FAILURE);
+                }
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected ResourceTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        String name = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; name == null && i < args.length; i++) {
+                if (args[i] instanceof String) {
+                    name = (String) args[i];
+                } else if (args[i] instanceof ResourceTO) {
+                    name = ((ResourceTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if (name != null) {
+            try {
+                return binder.getResourceTO(resourceDAO.find(name));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/RoleLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/RoleLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/RoleLogic.java
new file mode 100644
index 0000000..aee0d27
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/RoleLogic.java
@@ -0,0 +1,405 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Resource;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.to.BulkAction;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.AttributableTransformer;
+import org.apache.syncope.provisioning.api.RoleProvisioningManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.logic.data.RoleDataBinder;
+import org.apache.syncope.server.security.AuthContextUtil;
+import org.apache.syncope.server.security.UnauthorizedRoleException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionInterceptor;
+
+/**
+ * Note that this controller does not extend {@link AbstractTransactionalLogic}, hence does not provide any
+ * Spring's Transactional logic at class level.
+ */
+@Component
+public class RoleLogic extends AbstractSubjectLogic<RoleTO, RoleMod> {
+
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    @Autowired
+    protected UserDAO userDAO;
+
+    @Autowired
+    protected SubjectSearchDAO searchDAO;
+
+    @Autowired
+    protected RoleDataBinder binder;
+
+    @Autowired
+    protected PropagationManager propagationManager;
+
+    @Autowired
+    protected PropagationTaskExecutor taskExecutor;
+
+    @Autowired
+    protected AttributableTransformer attrTransformer;
+
+    @Resource(name = "anonymousUser")
+    protected String anonymousUser;
+
+    @Autowired
+    protected RoleProvisioningManager provisioningManager;
+
+    @PreAuthorize("hasAnyRole('ROLE_READ', T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+    @Transactional(readOnly = true)
+    @Override
+    public RoleTO read(final Long roleKey) {
+        Role role;
+        // bypass role entitlements check
+        if (anonymousUser.equals(AuthContextUtil.getAuthenticatedUsername())) {
+            role = roleDAO.find(roleKey);
+        } else {
+            role = roleDAO.authFetchRole(roleKey);
+        }
+
+        if (role == null) {
+            throw new NotFoundException("Role " + roleKey);
+        }
+
+        return binder.getRoleTO(role);
+    }
+
+    @PreAuthorize("isAuthenticated() "
+            + "and not(hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT))")
+    @Transactional(readOnly = true)
+    public RoleTO readSelf(final Long roleKey) {
+        // Explicit search instead of using binder.getRoleFromId() in order to bypass auth checks - will do here
+        Role role = roleDAO.find(roleKey);
+        if (role == null) {
+            throw new NotFoundException("Role " + roleKey);
+        }
+
+        Set<Long> ownedRoleIds;
+        User authUser = userDAO.find(AuthContextUtil.getAuthenticatedUsername());
+        if (authUser == null) {
+            ownedRoleIds = Collections.<Long>emptySet();
+        } else {
+            ownedRoleIds = authUser.getRoleIds();
+        }
+
+        Set<Long> allowedRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+        allowedRoleIds.addAll(ownedRoleIds);
+        if (!allowedRoleIds.contains(role.getKey())) {
+            throw new UnauthorizedRoleException(role.getKey());
+        }
+
+        return binder.getRoleTO(role);
+    }
+
+    @PreAuthorize("hasRole('ROLE_READ')")
+    @Transactional(readOnly = true)
+    public RoleTO parent(final Long roleKey) {
+        Role role = roleDAO.authFetchRole(roleKey);
+
+        Set<Long> allowedRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+        if (role.getParent() != null && !allowedRoleIds.contains(role.getParent().getKey())) {
+            throw new UnauthorizedRoleException(role.getParent().getKey());
+        }
+
+        RoleTO result = role.getParent() == null
+                ? null
+                : binder.getRoleTO(role.getParent());
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('ROLE_READ')")
+    @Transactional(readOnly = true)
+    public List<RoleTO> children(final Long roleKey) {
+        Role role = roleDAO.authFetchRole(roleKey);
+
+        Set<Long> allowedRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+
+        List<Role> children = roleDAO.findChildren(role);
+        List<RoleTO> childrenTOs = new ArrayList<RoleTO>(children.size());
+        for (Role child : children) {
+            if (allowedRoleIds.contains(child.getKey())) {
+                childrenTOs.add(binder.getRoleTO(child));
+            }
+        }
+
+        return childrenTOs;
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public int count() {
+        return roleDAO.count();
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
+    @Override
+    public List<RoleTO> list(final int page, final int size, final List<OrderByClause> orderBy) {
+        List<Role> roles = roleDAO.findAll(page, size, orderBy);
+
+        List<RoleTO> roleTOs = new ArrayList<>(roles.size());
+        for (Role role : roles) {
+            roleTOs.add(binder.getRoleTO(role));
+        }
+
+        return roleTOs;
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public int searchCount(final SearchCond searchCondition) {
+        final Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+        return searchDAO.count(adminRoleIds, searchCondition, SubjectType.ROLE);
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public List<RoleTO> search(final SearchCond searchCondition, final int page, final int size,
+            final List<OrderByClause> orderBy) {
+
+        final List<Role> matchingRoles = searchDAO.search(
+                RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()),
+                searchCondition, page, size, orderBy, SubjectType.ROLE);
+
+        final List<RoleTO> result = new ArrayList<>(matchingRoles.size());
+        for (Role role : matchingRoles) {
+            result.add(binder.getRoleTO(role));
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('ROLE_CREATE')")
+    public RoleTO create(final RoleTO roleTO) {
+        // Check that this operation is allowed to be performed by caller
+        Set<Long> allowedRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+        if (roleTO.getParent() != 0 && !allowedRoleIds.contains(roleTO.getParent())) {
+            throw new UnauthorizedRoleException(roleTO.getParent());
+        }
+
+        // Attributable transformation (if configured)
+        RoleTO actual = attrTransformer.transform(roleTO);
+        LOG.debug("Transformed: {}", actual);
+
+        /*
+         * Actual operations: workflow, propagation
+         */
+        Map.Entry<Long, List<PropagationStatus>> created = provisioningManager.create(roleTO);
+        final RoleTO savedTO = binder.getRoleTO(created.getKey());
+        savedTO.getPropagationStatusTOs().addAll(created.getValue());
+        return savedTO;
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Override
+    public RoleTO update(final RoleMod roleMod) {
+        // Check that this operation is allowed to be performed by caller
+        roleDAO.authFetchRole(roleMod.getKey());
+
+        // Attribute value transformation (if configured)
+        RoleMod actual = attrTransformer.transform(roleMod);
+        LOG.debug("Transformed: {}", actual);
+
+        Map.Entry<Long, List<PropagationStatus>> updated = provisioningManager.update(roleMod);
+
+        final RoleTO updatedTO = binder.getRoleTO(updated.getKey());
+        updatedTO.getPropagationStatusTOs().addAll(updated.getValue());
+        return updatedTO;
+    }
+
+    @PreAuthorize("hasRole('ROLE_DELETE')")
+    @Override
+    public RoleTO delete(final Long roleKey) {
+        List<Role> ownedRoles = roleDAO.findOwnedByRole(roleKey);
+        if (!ownedRoles.isEmpty()) {
+            List<String> owned = new ArrayList<String>(ownedRoles.size());
+            for (Role role : ownedRoles) {
+                owned.add(role.getKey() + " " + role.getName());
+            }
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RoleOwnership);
+            sce.getElements().addAll(owned);
+            throw sce;
+        }
+
+        List<PropagationStatus> statuses = provisioningManager.delete(roleKey);
+
+        RoleTO roleTO = new RoleTO();
+        roleTO.setKey(roleKey);
+
+        roleTO.getPropagationStatusTOs().addAll(statuses);
+
+        return roleTO;
+    }
+
+    @PreAuthorize("(hasRole('ROLE_DELETE') and #bulkAction.operation == #bulkAction.operation.DELETE)")
+    public BulkActionResult bulk(final BulkAction bulkAction) {
+        BulkActionResult res = new BulkActionResult();
+
+        if (bulkAction.getOperation() == BulkAction.Type.DELETE) {
+            for (String roleKey : bulkAction.getTargets()) {
+                try {
+                    res.add(delete(Long.valueOf(roleKey)).getKey(), BulkActionResult.Status.SUCCESS);
+                } catch (Exception e) {
+                    LOG.error("Error performing delete for role {}", roleKey, e);
+                    res.add(roleKey, BulkActionResult.Status.FAILURE);
+                }
+            }
+        } else {
+            LOG.warn("Unsupported bulk action: {}", bulkAction.getOperation());
+        }
+
+        return res;
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO unlink(final Long roleKey, final Collection<String> resources) {
+        final RoleMod roleMod = new RoleMod();
+        roleMod.setKey(roleKey);
+        roleMod.getResourcesToRemove().addAll(resources);
+        final Long updatedResult = provisioningManager.unlink(roleMod);
+
+        return binder.getRoleTO(updatedResult);
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO link(final Long roleKey, final Collection<String> resources) {
+        final RoleMod roleMod = new RoleMod();
+        roleMod.setKey(roleKey);
+        roleMod.getResourcesToAdd().addAll(resources);
+        return binder.getRoleTO(provisioningManager.link(roleMod));
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO unassign(final Long roleKey, final Collection<String> resources) {
+        final RoleMod roleMod = new RoleMod();
+        roleMod.setKey(roleKey);
+        roleMod.getResourcesToRemove().addAll(resources);
+        return update(roleMod);
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO assign(
+            final Long roleKey, final Collection<String> resources, final boolean changePwd, final String password) {
+
+        final RoleMod userMod = new RoleMod();
+        userMod.setKey(roleKey);
+        userMod.getResourcesToAdd().addAll(resources);
+        return update(userMod);
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO deprovision(final Long roleKey, final Collection<String> resources) {
+        final Role role = roleDAO.authFetchRole(roleKey);
+
+        List<PropagationStatus> statuses = provisioningManager.deprovision(roleKey, resources);
+
+        final RoleTO updatedTO = binder.getRoleTO(role);
+        updatedTO.getPropagationStatusTOs().addAll(statuses);
+        return updatedTO;
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO provision(
+            final Long roleKey, final Collection<String> resources, final boolean changePwd, final String password) {
+        final RoleTO original = binder.getRoleTO(roleKey);
+
+        //trick: assign and retrieve propagation statuses ...
+        original.getPropagationStatusTOs().addAll(
+                assign(roleKey, resources, changePwd, password).getPropagationStatusTOs());
+
+        // .... rollback.
+        TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
+        return original;
+    }
+
+    @Override
+    protected RoleTO resolveReference(final Method method, final Object... args) throws UnresolvedReferenceException {
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof RoleTO) {
+                    id = ((RoleTO) args[i]).getKey();
+                } else if (args[i] instanceof RoleMod) {
+                    id = ((RoleMod) args[i]).getKey();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return binder.getRoleTO(id);
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SchemaLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SchemaLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SchemaLogic.java
new file mode 100644
index 0000000..675bd42
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SchemaLogic.java
@@ -0,0 +1,328 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AbstractSchemaTO;
+import org.apache.syncope.common.lib.to.DerSchemaTO;
+import org.apache.syncope.common.lib.to.PlainSchemaTO;
+import org.apache.syncope.common.lib.to.VirSchemaTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.SchemaType;
+import org.apache.syncope.persistence.api.dao.DerSchemaDAO;
+import org.apache.syncope.persistence.api.dao.DuplicateException;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.persistence.api.entity.DerSchema;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.persistence.api.entity.VirSchema;
+import org.apache.syncope.server.logic.data.SchemaDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SchemaLogic extends AbstractTransactionalLogic<AbstractSchemaTO> {
+
+    @Autowired
+    private PlainSchemaDAO plainSchemaDAO;
+
+    @Autowired
+    private DerSchemaDAO derSchemaDAO;
+
+    @Autowired
+    private VirSchemaDAO virSchemaDAO;
+
+    @Autowired
+    private SchemaDataBinder binder;
+
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
+    private boolean doesSchemaExist(final SchemaType schemaType, final String name, final AttributableUtil attrUtil) {
+        boolean found;
+
+        switch (schemaType) {
+            case VIRTUAL:
+                found = virSchemaDAO.find(name, attrUtil.virSchemaClass()) != null;
+                break;
+
+            case DERIVED:
+                found = derSchemaDAO.find(name, attrUtil.derSchemaClass()) != null;
+                break;
+
+            case PLAIN:
+                found = plainSchemaDAO.find(name, attrUtil.plainSchemaClass()) != null;
+                break;
+
+            default:
+                found = false;
+        }
+
+        return found;
+    }
+
+    @PreAuthorize("hasRole('SCHEMA_CREATE')")
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractSchemaTO> T create(
+            final AttributableType attrType, final SchemaType schemaType, final T schemaTO) {
+
+        if (StringUtils.isBlank(schemaTO.getKey())) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
+            sce.getElements().add("Schema name");
+            throw sce;
+        }
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(attrType);
+
+        if (doesSchemaExist(schemaType, schemaTO.getKey(), attrUtil)) {
+            throw new DuplicateException(schemaType + "/" + attrType + "/" + schemaTO.getKey());
+        }
+
+        T created;
+        switch (schemaType) {
+            case VIRTUAL:
+                VirSchema virSchema = attrUtil.newVirSchema();
+                binder.create((VirSchemaTO) schemaTO, virSchema);
+                virSchema = virSchemaDAO.save(virSchema);
+                created = (T) binder.getVirSchemaTO(virSchema);
+                break;
+            case DERIVED:
+                DerSchema derSchema = attrUtil.newDerSchema();
+                binder.create((DerSchemaTO) schemaTO, derSchema);
+                derSchema = derSchemaDAO.save(derSchema);
+
+                created = (T) binder.getDerSchemaTO(derSchema);
+                break;
+
+            case PLAIN:
+            default:
+                PlainSchema normalSchema = attrUtil.newPlainSchema();
+                binder.create((PlainSchemaTO) schemaTO, normalSchema);
+                normalSchema = plainSchemaDAO.save(normalSchema);
+
+                created = (T) binder.getSchemaTO(normalSchema, attrUtil);
+        }
+        return created;
+    }
+
+    @PreAuthorize("hasRole('SCHEMA_DELETE')")
+    public void delete(final AttributableType attrType, final SchemaType schemaType, final String schemaName) {
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(attrType);
+
+        if (!doesSchemaExist(schemaType, schemaName, attrUtil)) {
+            throw new NotFoundException(schemaType + "/" + attrType + "/" + schemaName);
+        }
+
+        switch (schemaType) {
+            case VIRTUAL:
+                virSchemaDAO.delete(schemaName, attrUtil);
+                break;
+
+            case DERIVED:
+                derSchemaDAO.delete(schemaName, attrUtil);
+                break;
+
+            case PLAIN:
+            default:
+                plainSchemaDAO.delete(schemaName, attrUtil);
+        }
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractSchemaTO> List<T> list(final AttributableType attrType, final SchemaType schemaType) {
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(attrType);
+
+        List<T> result;
+        switch (schemaType) {
+            case VIRTUAL:
+                List<VirSchema> virSchemas = virSchemaDAO.findAll(attrUtil.virSchemaClass());
+                result = new ArrayList<>(virSchemas.size());
+                for (VirSchema derSchema : virSchemas) {
+                    result.add((T) binder.getVirSchemaTO(derSchema));
+                }
+                break;
+
+            case DERIVED:
+                List<DerSchema> derSchemas = derSchemaDAO.findAll(attrUtil.derSchemaClass());
+                result = new ArrayList<>(derSchemas.size());
+                for (DerSchema derSchema : derSchemas) {
+                    result.add((T) binder.getDerSchemaTO(derSchema));
+                }
+                break;
+
+            case PLAIN:
+            default:
+                List<PlainSchema> schemas = plainSchemaDAO.findAll(attrUtil.plainSchemaClass());
+                result = new ArrayList<>(schemas.size());
+                for (PlainSchema schema : schemas) {
+                    result.add((T) binder.getSchemaTO(schema, attrUtil));
+                }
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('SCHEMA_READ')")
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractSchemaTO> T read(
+            final AttributableType attrType, final SchemaType schemaType, final String schemaName) {
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(attrType);
+
+        T read;
+        switch (schemaType) {
+            case VIRTUAL:
+                VirSchema virSchema = virSchemaDAO.find(schemaName, attrUtil.virSchemaClass());
+                if (virSchema == null) {
+                    throw new NotFoundException("Virtual Schema '" + schemaName + "'");
+                }
+
+                read = (T) binder.getVirSchemaTO(virSchema);
+                break;
+
+            case DERIVED:
+                DerSchema derSchema = derSchemaDAO.find(schemaName, attrUtil.derSchemaClass());
+                if (derSchema == null) {
+                    throw new NotFoundException("Derived schema '" + schemaName + "'");
+                }
+
+                read = (T) binder.getDerSchemaTO(derSchema);
+                break;
+
+            case PLAIN:
+            default:
+                PlainSchema schema = plainSchemaDAO.find(schemaName, attrUtil.plainSchemaClass());
+                if (schema == null) {
+                    throw new NotFoundException("Schema '" + schemaName + "'");
+                }
+
+                read = (T) binder.getSchemaTO(schema, attrUtil);
+        }
+
+        return read;
+    }
+
+    @PreAuthorize("hasRole('SCHEMA_UPDATE')")
+    public <T extends AbstractSchemaTO> void update(
+            final AttributableType attrType, final SchemaType schemaType, final T schemaTO) {
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(attrType);
+
+        if (!doesSchemaExist(schemaType, schemaTO.getKey(), attrUtil)) {
+            throw new NotFoundException(schemaType + "/" + attrType + "/" + schemaTO.getKey());
+        }
+
+        switch (schemaType) {
+            case VIRTUAL:
+                VirSchema virSchema = virSchemaDAO.find(schemaTO.getKey(), attrUtil.virSchemaClass());
+                if (virSchema == null) {
+                    throw new NotFoundException("Virtual Schema '" + schemaTO.getKey() + "'");
+                }
+
+                binder.update((VirSchemaTO) schemaTO, virSchema);
+                virSchemaDAO.save(virSchema);
+                break;
+
+            case DERIVED:
+                DerSchema derSchema = derSchemaDAO.find(schemaTO.getKey(), attrUtil.derSchemaClass());
+                if (derSchema == null) {
+                    throw new NotFoundException("Derived schema '" + schemaTO.getKey() + "'");
+                }
+
+                binder.update((DerSchemaTO) schemaTO, derSchema);
+                derSchemaDAO.save(derSchema);
+                break;
+
+            case PLAIN:
+            default:
+                PlainSchema schema = plainSchemaDAO.find(schemaTO.getKey(), attrUtil.plainSchemaClass());
+                if (schema == null) {
+                    throw new NotFoundException("Schema '" + schemaTO.getKey() + "'");
+                }
+
+                binder.update((PlainSchemaTO) schemaTO, schema, attrUtil);
+                plainSchemaDAO.save(schema);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected AbstractSchemaTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        String kind = null;
+        String name = null;
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; (name == null || kind == null) && i < args.length; i++) {
+                if (args[i] instanceof String) {
+                    if (kind == null) {
+                        kind = (String) args[i];
+                    } else {
+                        name = (String) args[i];
+                    }
+                } else if (args[i] instanceof AbstractSchemaTO) {
+                    name = ((AbstractSchemaTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if (name != null) {
+            try {
+                final AttributableUtil attrUtil = attrUtilFactory.getInstance(kind);
+
+                AbstractSchemaTO result = null;
+
+                PlainSchema plainSchema = plainSchemaDAO.find(name, attrUtil.plainSchemaClass());
+                if (plainSchema == null) {
+                    DerSchema derSchema = derSchemaDAO.find(name, attrUtil.derSchemaClass());
+                    if (derSchema == null) {
+                        VirSchema virSchema = virSchemaDAO.find(name, attrUtil.virSchemaClass());
+                        if (virSchema != null) {
+                            result = binder.getVirSchemaTO(virSchema);
+                        }
+                    } else {
+                        result = binder.getDerSchemaTO(derSchema);
+                    }
+                } else {
+                    result = binder.getSchemaTO(plainSchema, attrUtil);
+                }
+
+                return result;
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SecurityQuestionLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SecurityQuestionLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SecurityQuestionLogic.java
new file mode 100644
index 0000000..01b2a57
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SecurityQuestionLogic.java
@@ -0,0 +1,150 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.to.SecurityQuestionTO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.SecurityQuestionDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.entity.user.SecurityQuestion;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.server.logic.data.SecurityQuestionDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SecurityQuestionLogic extends AbstractTransactionalLogic<SecurityQuestionTO> {
+
+    @Autowired
+    private SecurityQuestionDAO securityQuestionDAO;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private SecurityQuestionDataBinder binder;
+
+    @PreAuthorize("isAuthenticated()")
+    public List<SecurityQuestionTO> list() {
+        List<SecurityQuestionTO> result = new ArrayList<SecurityQuestionTO>();
+        for (SecurityQuestion securityQuestion : securityQuestionDAO.findAll()) {
+            result.add(binder.getSecurityQuestionTO(securityQuestion));
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    public SecurityQuestionTO read(final Long securityQuestionId) {
+        SecurityQuestion securityQuestion = securityQuestionDAO.find(securityQuestionId);
+        if (securityQuestion == null) {
+            LOG.error("Could not find security question '" + securityQuestionId + "'");
+
+            throw new NotFoundException(String.valueOf(securityQuestionId));
+        }
+
+        return binder.getSecurityQuestionTO(securityQuestion);
+    }
+
+    @PreAuthorize("hasRole('SECURITY_QUESTION_CREATE')")
+    public SecurityQuestionTO create(final SecurityQuestionTO securityQuestionTO) {
+        return binder.getSecurityQuestionTO(securityQuestionDAO.save(binder.create(securityQuestionTO)));
+    }
+
+    @PreAuthorize("hasRole('SECURITY_QUESTION_UPDATE')")
+    public SecurityQuestionTO update(final SecurityQuestionTO securityQuestionTO) {
+        SecurityQuestion securityQuestion = securityQuestionDAO.find(securityQuestionTO.getKey());
+        if (securityQuestion == null) {
+            LOG.error("Could not find security question '" + securityQuestionTO.getKey() + "'");
+
+            throw new NotFoundException(String.valueOf(securityQuestionTO.getKey()));
+        }
+
+        binder.update(securityQuestion, securityQuestionTO);
+        securityQuestion = securityQuestionDAO.save(securityQuestion);
+
+        return binder.getSecurityQuestionTO(securityQuestion);
+    }
+
+    @PreAuthorize("hasRole('SECURITY_QUESTION_DELETE')")
+    public SecurityQuestionTO delete(final Long securityQuestionId) {
+        SecurityQuestion securityQuestion = securityQuestionDAO.find(securityQuestionId);
+        if (securityQuestion == null) {
+            LOG.error("Could not find security question '" + securityQuestionId + "'");
+
+            throw new NotFoundException(String.valueOf(securityQuestionId));
+        }
+
+        SecurityQuestionTO deleted = binder.getSecurityQuestionTO(securityQuestion);
+        securityQuestionDAO.delete(securityQuestionId);
+        return deleted;
+    }
+
+    @PreAuthorize("isAnonymous() or hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+    public SecurityQuestionTO read(final String username) {
+        if (username == null) {
+            throw new NotFoundException("Null username");
+        }
+        User user = userDAO.find(username);
+        if (user == null) {
+            throw new NotFoundException("User " + username);
+        }
+
+        if (user.getSecurityQuestion() == null) {
+            LOG.error("Could not find security question for user '" + username + "'");
+
+            throw new NotFoundException("Security question for user " + username);
+        }
+
+        return binder.getSecurityQuestionTO(user.getSecurityQuestion());
+    }
+
+    @Override
+    protected SecurityQuestionTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof SecurityQuestionTO) {
+                    id = ((SecurityQuestionTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return binder.getSecurityQuestionTO(securityQuestionDAO.find(id));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/TaskLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/TaskLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/TaskLogic.java
new file mode 100644
index 0000000..11c9b4b
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/TaskLogic.java
@@ -0,0 +1,408 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AbstractTaskTO;
+import org.apache.syncope.common.lib.to.BulkAction;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.SchedTaskTO;
+import org.apache.syncope.common.lib.to.SyncTaskTO;
+import org.apache.syncope.common.lib.to.TaskExecTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.PropagationMode;
+import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.TaskDAO;
+import org.apache.syncope.persistence.api.dao.TaskExecDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.persistence.api.entity.task.SchedTask;
+import org.apache.syncope.persistence.api.entity.task.Task;
+import org.apache.syncope.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.persistence.api.entity.task.TaskUtil;
+import org.apache.syncope.persistence.api.entity.task.TaskUtilFactory;
+import org.apache.syncope.provisioning.api.job.TaskJob;
+import org.apache.syncope.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.logic.data.TaskDataBinder;
+import org.apache.syncope.server.logic.init.ImplementationClassNamesLoader;
+import org.apache.syncope.server.logic.init.JobInstanceLoader;
+import org.apache.syncope.server.logic.notification.NotificationJob;
+import org.quartz.JobDataMap;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class TaskLogic extends AbstractTransactionalLogic<AbstractTaskTO> {
+
+    @Autowired
+    private TaskDAO taskDAO;
+
+    @Autowired
+    private TaskExecDAO taskExecDAO;
+
+    @Autowired
+    private TaskDataBinder binder;
+
+    @Autowired
+    private PropagationTaskExecutor taskExecutor;
+
+    @Autowired
+    private NotificationJob notificationJob;
+
+    @Autowired
+    private JobInstanceLoader jobInstanceLoader;
+
+    @Autowired
+    private SchedulerFactoryBean scheduler;
+
+    @Autowired
+    private ImplementationClassNamesLoader classNamesLoader;
+
+    @Autowired
+    private TaskUtilFactory taskUtilFactory;
+
+    @PreAuthorize("hasRole('TASK_CREATE')")
+    public <T extends SchedTaskTO> T createSchedTask(final T taskTO) {
+        TaskUtil taskUtil = taskUtilFactory.getInstance(taskTO);
+
+        SchedTask task = binder.createSchedTask(taskTO, taskUtil);
+        task = taskDAO.save(task);
+
+        try {
+            jobInstanceLoader.registerJob(task, task.getJobClassName(), task.getCronExpression());
+        } catch (Exception e) {
+            LOG.error("While registering quartz job for task " + task.getKey(), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+
+        return binder.getTaskTO(task, taskUtil);
+    }
+
+    @PreAuthorize("hasRole('TASK_UPDATE')")
+    public SyncTaskTO updateSync(final SyncTaskTO taskTO) {
+        return updateSched(taskTO);
+    }
+
+    @PreAuthorize("hasRole('TASK_UPDATE')")
+    public <T extends SchedTaskTO> T updateSched(final SchedTaskTO taskTO) {
+        SchedTask task = taskDAO.find(taskTO.getId());
+        if (task == null) {
+            throw new NotFoundException("Task " + taskTO.getId());
+        }
+
+        TaskUtil taskUtil = taskUtilFactory.getInstance(task);
+
+        binder.updateSchedTask(task, taskTO, taskUtil);
+        task = taskDAO.save(task);
+
+        try {
+            jobInstanceLoader.registerJob(task, task.getJobClassName(), task.getCronExpression());
+        } catch (Exception e) {
+            LOG.error("While registering quartz job for task " + task.getKey(), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+
+        return binder.getTaskTO(task, taskUtil);
+    }
+
+    @PreAuthorize("hasRole('TASK_LIST')")
+    public int count(final TaskType taskType) {
+        return taskDAO.count(taskType);
+    }
+
+    @PreAuthorize("hasRole('TASK_LIST')")
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractTaskTO> List<T> list(final TaskType taskType,
+            final int page, final int size, final List<OrderByClause> orderByClauses) {
+
+        TaskUtil taskUtil = taskUtilFactory.getInstance(taskType);
+
+        List<Task> tasks = taskDAO.findAll(page, size, orderByClauses, taskType);
+        List<T> taskTOs = new ArrayList<>(tasks.size());
+        for (Task task : tasks) {
+            taskTOs.add((T) binder.getTaskTO(task, taskUtil));
+        }
+
+        return taskTOs;
+    }
+
+    @PreAuthorize("hasRole('TASK_LIST')")
+    public Set<String> getJobClasses() {
+        return classNamesLoader.getClassNames(ImplementationClassNamesLoader.Type.TASKJOB);
+    }
+
+    @PreAuthorize("hasRole('TASK_LIST')")
+    public Set<String> getSyncActionsClasses() {
+        return classNamesLoader.getClassNames(ImplementationClassNamesLoader.Type.SYNC_ACTIONS);
+    }
+
+    @PreAuthorize("hasRole('TASK_LIST')")
+    public Set<String> getPushActionsClasses() {
+        return classNamesLoader.getClassNames(ImplementationClassNamesLoader.Type.PUSH_ACTIONS);
+    }
+
+    @PreAuthorize("hasRole('TASK_READ')")
+    public <T extends AbstractTaskTO> T read(final Long taskId) {
+        Task task = taskDAO.find(taskId);
+        if (task == null) {
+            throw new NotFoundException("Task " + taskId);
+        }
+        return binder.getTaskTO(task, taskUtilFactory.getInstance(task));
+    }
+
+    @PreAuthorize("hasRole('TASK_READ')")
+    public TaskExecTO readExecution(final Long executionId) {
+        TaskExec taskExec = taskExecDAO.find(executionId);
+        if (taskExec == null) {
+            throw new NotFoundException("Task execution " + executionId);
+        }
+        return binder.getTaskExecTO(taskExec);
+    }
+
+    @PreAuthorize("hasRole('TASK_EXECUTE')")
+    public TaskExecTO execute(final Long taskId, final boolean dryRun) {
+        Task task = taskDAO.find(taskId);
+        if (task == null) {
+            throw new NotFoundException("Task " + taskId);
+        }
+        TaskUtil taskUtil = taskUtilFactory.getInstance(task);
+
+        TaskExecTO result = null;
+        switch (taskUtil.getType()) {
+            case PROPAGATION:
+                final TaskExec propExec = taskExecutor.execute((PropagationTask) task);
+                result = binder.getTaskExecTO(propExec);
+                break;
+
+            case NOTIFICATION:
+                final TaskExec notExec = notificationJob.executeSingle((NotificationTask) task);
+                result = binder.getTaskExecTO(notExec);
+                break;
+
+            case SCHEDULED:
+            case SYNCHRONIZATION:
+            case PUSH:
+                try {
+                    jobInstanceLoader.registerJob(task,
+                            ((SchedTask) task).getJobClassName(),
+                            ((SchedTask) task).getCronExpression());
+
+                    JobDataMap map = new JobDataMap();
+                    map.put(TaskJob.DRY_RUN_JOBDETAIL_KEY, dryRun);
+
+                    scheduler.getScheduler().triggerJob(
+                            new JobKey(JobInstanceLoader.getJobName(task), Scheduler.DEFAULT_GROUP), map);
+                } catch (Exception e) {
+                    LOG.error("While executing task {}", task, e);
+
+                    SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+                    sce.getElements().add(e.getMessage());
+                    throw sce;
+                }
+
+                result = new TaskExecTO();
+                result.setTask(taskId);
+                result.setStartDate(new Date());
+                result.setStatus("JOB_FIRED");
+                result.setMessage("Job fired; waiting for results...");
+                break;
+
+            default:
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('TASK_READ')")
+    public TaskExecTO report(final Long executionId, final PropagationTaskExecStatus status, final String message) {
+        TaskExec exec = taskExecDAO.find(executionId);
+        if (exec == null) {
+            throw new NotFoundException("Task execution " + executionId);
+        }
+
+        SyncopeClientException sce = SyncopeClientException.build(
+                ClientExceptionType.InvalidPropagationTaskExecReport);
+
+        TaskUtil taskUtil = taskUtilFactory.getInstance(exec.getTask());
+        if (TaskType.PROPAGATION == taskUtil.getType()) {
+            PropagationTask task = (PropagationTask) exec.getTask();
+            if (task.getPropagationMode() != PropagationMode.TWO_PHASES) {
+                sce.getElements().add("Propagation mode: " + task.getPropagationMode());
+            }
+        } else {
+            sce.getElements().add("Task type: " + taskUtil);
+        }
+
+        switch (status) {
+            case SUCCESS:
+            case FAILURE:
+                break;
+
+            case CREATED:
+            case SUBMITTED:
+            case UNSUBMITTED:
+                sce.getElements().add("Execution status to be set: " + status);
+                break;
+
+            default:
+        }
+
+        if (!sce.isEmpty()) {
+            throw sce;
+        }
+
+        exec.setStatus(status.toString());
+        exec.setMessage(message);
+        return binder.getTaskExecTO(taskExecDAO.save(exec));
+    }
+
+    @PreAuthorize("hasRole('TASK_DELETE')")
+    public <T extends AbstractTaskTO> T delete(final Long taskId) {
+        Task task = taskDAO.find(taskId);
+        if (task == null) {
+            throw new NotFoundException("Task " + taskId);
+        }
+        TaskUtil taskUtil = taskUtilFactory.getInstance(task);
+
+        T taskToDelete = binder.getTaskTO(task, taskUtil);
+
+        if (TaskType.SCHEDULED == taskUtil.getType()
+                || TaskType.SYNCHRONIZATION == taskUtil.getType()
+                || TaskType.PUSH == taskUtil.getType()) {
+            jobInstanceLoader.unregisterJob(task);
+        }
+
+        taskDAO.delete(task);
+        return taskToDelete;
+    }
+
+    @PreAuthorize("hasRole('TASK_DELETE')")
+    public TaskExecTO deleteExecution(final Long executionId) {
+        TaskExec taskExec = taskExecDAO.find(executionId);
+        if (taskExec == null) {
+            throw new NotFoundException("Task execution " + executionId);
+        }
+
+        TaskExecTO taskExecutionToDelete = binder.getTaskExecTO(taskExec);
+        taskExecDAO.delete(taskExec);
+        return taskExecutionToDelete;
+    }
+
+    @PreAuthorize("(hasRole('TASK_DELETE') and #bulkAction.operation == #bulkAction.operation.DELETE) or "
+            + "(hasRole('TASK_EXECUTE') and "
+            + "(#bulkAction.operation == #bulkAction.operation.EXECUTE or "
+            + "#bulkAction.operation == #bulkAction.operation.DRYRUN))")
+    public BulkActionResult bulk(final BulkAction bulkAction) {
+        BulkActionResult res = new BulkActionResult();
+
+        switch (bulkAction.getOperation()) {
+            case DELETE:
+                for (String taskId : bulkAction.getTargets()) {
+                    try {
+                        res.add(delete(Long.valueOf(taskId)).getId(), BulkActionResult.Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing delete for task {}", taskId, e);
+                        res.add(taskId, BulkActionResult.Status.FAILURE);
+                    }
+                }
+                break;
+
+            case DRYRUN:
+                for (String taskId : bulkAction.getTargets()) {
+                    try {
+                        execute(Long.valueOf(taskId), true);
+                        res.add(taskId, BulkActionResult.Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing dryrun for task {}", taskId, e);
+                        res.add(taskId, BulkActionResult.Status.FAILURE);
+                    }
+                }
+                break;
+
+            case EXECUTE:
+                for (String taskId : bulkAction.getTargets()) {
+                    try {
+                        execute(Long.valueOf(taskId), false);
+                        res.add(taskId, BulkActionResult.Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing execute for task {}", taskId, e);
+                        res.add(taskId, BulkActionResult.Status.FAILURE);
+                    }
+                }
+                break;
+
+            default:
+        }
+
+        return res;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected AbstractTaskTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args)
+                && !"deleteExecution".equals(method.getName()) && !"readExecution".equals(method.getName())) {
+
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof AbstractTaskTO) {
+                    id = ((AbstractTaskTO) args[i]).getId();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                final Task task = taskDAO.find(id);
+                return binder.getTaskTO(task, taskUtilFactory.getInstance(task));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UnresolvedReferenceException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UnresolvedReferenceException.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UnresolvedReferenceException.java
new file mode 100644
index 0000000..aff28d9
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UnresolvedReferenceException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.server.logic;
+
+/**
+ * Indicates unresolved bean reference.
+ */
+public class UnresolvedReferenceException extends Exception {
+
+    private static final long serialVersionUID = -675489116009955632L;
+
+    public UnresolvedReferenceException() {
+        super();
+    }
+
+    public UnresolvedReferenceException(final Throwable cause) {
+        super(cause);
+    }
+}