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/05/22 12:24:44 UTC

[5/5] syncope git commit: [SYNCOPE-670] Merge from 1_2_X

[SYNCOPE-670] Merge from 1_2_X


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

Branch: refs/heads/master
Commit: 22a9e12e5e001de4926deb9052ba28bd98a85d62
Parents: d489e8c 15cca15
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Fri May 22 12:24:30 2015 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri May 22 12:24:30 2015 +0200

----------------------------------------------------------------------
 .../common/rest/api/service/ReportService.java  |  14 +--
 .../common/rest/api/service/TaskService.java    |  14 +--
 .../syncope/core/logic/AbstractJobLogic.java    | 109 +++++++++++--------
 .../apache/syncope/core/logic/ReportLogic.java  |   8 +-
 .../apache/syncope/core/logic/TaskLogic.java    |  12 +-
 .../apache/syncope/core/logic/UserLogic.java    |   2 +-
 .../core/provisioning/java/VirAttrHandler.java  |   2 +-
 .../propagation/PropagationManagerImpl.java     |  31 +++---
 .../rest/cxf/service/ReportServiceImpl.java     |   8 +-
 .../core/rest/cxf/service/TaskServiceImpl.java  |   8 +-
 .../activiti/ActivitiUserWorkflowAdapter.java   |   6 +-
 .../core/workflow/activiti/task/Update.java     |  11 +-
 .../fit/core/reference/SchedTaskITCase.java     |  28 ++---
 13 files changed, 138 insertions(+), 115 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/22a9e12e/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReportService.java
----------------------------------------------------------------------
diff --cc common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReportService.java
index fc37192,0000000..996f784
mode 100644,000000..100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReportService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReportService.java
@@@ -1,182 -1,0 +1,182 @@@
 +/*
 + * 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.common.rest.api.service;
 +
 +import java.util.List;
 +import javax.validation.constraints.NotNull;
 +import javax.ws.rs.BeanParam;
 +import javax.ws.rs.Consumes;
 +import javax.ws.rs.DELETE;
 +import javax.ws.rs.GET;
 +import javax.ws.rs.MatrixParam;
 +import javax.ws.rs.POST;
 +import javax.ws.rs.PUT;
 +import javax.ws.rs.Path;
 +import javax.ws.rs.PathParam;
 +import javax.ws.rs.Produces;
 +import javax.ws.rs.QueryParam;
 +import javax.ws.rs.core.MediaType;
 +import javax.ws.rs.core.Response;
 +import org.apache.cxf.jaxrs.model.wadl.Description;
 +import org.apache.cxf.jaxrs.model.wadl.Descriptions;
 +import org.apache.cxf.jaxrs.model.wadl.DocTarget;
 +import org.apache.syncope.common.lib.to.PagedResult;
 +import org.apache.syncope.common.lib.to.ReportExecTO;
 +import org.apache.syncope.common.lib.to.ReportTO;
 +import org.apache.syncope.common.lib.types.JobAction;
 +import org.apache.syncope.common.lib.types.JobStatusType;
 +import org.apache.syncope.common.lib.types.ReportExecExportFormat;
 +import org.apache.syncope.common.lib.wrap.ReportletConfClass;
 +import org.apache.syncope.common.rest.api.beans.ListQuery;
 +
 +/**
 + * REST operations for reports.
 + */
 +@Path("reports")
 +public interface ReportService extends JAXRSService {
 +
 +    /**
 +     * Returns a list of available classes for reportlet configuration.
 +     *
 +     * @return list of available classes for reportlet configuration
 +     */
 +    @GET
 +    @Path("reportletConfClasses")
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    List<ReportletConfClass> getReportletConfClasses();
 +
 +    /**
 +     * Returns report with matching key.
 +     *
 +     * @param reportKey key of report to be read
 +     * @return report with matching key
 +     */
 +    @GET
 +    @Path("{reportKey}")
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    ReportTO read(@NotNull @PathParam("reportKey") Long reportKey);
 +
 +    /**
 +     * Returns report execution with matching key.
 +     *
 +     * @param executionKey report execution id to be selected
 +     * @return report execution with matching key
 +     */
 +    @GET
 +    @Path("executions/{executionKey}")
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    ReportExecTO readExecution(@NotNull @PathParam("executionKey") Long executionKey);
 +
 +    /**
 +     * Returns a paged list of all existing reports matching the given query;
 +     *
 +     * @param listQuery query conditions
 +     * @return paged list of existing reports matching the given query
 +     */
 +    @GET
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    PagedResult<ReportTO> list(@BeanParam ListQuery listQuery);
 +
 +    /**
 +     * Creates a new report.
 +     *
 +     * @param reportTO report to be created
 +     * @return <tt>Response</tt> object featuring <tt>Location</tt> header of created report
 +     */
 +    @Descriptions({
 +        @Description(target = DocTarget.RESPONSE, value = "Featuring <tt>Location</tt> header of created report")
 +    })
 +    @POST
 +    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    Response create(@NotNull ReportTO reportTO);
 +
 +    /**
 +     * Updates report with matching key.
 +     *
 +     * @param reportKey id for report to be updated
 +     * @param reportTO report to be stored
 +     */
 +    @PUT
 +    @Path("{reportKey}")
 +    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    void update(@NotNull @PathParam("reportKey") Long reportKey, ReportTO reportTO);
 +
 +    /**
 +     * Deletes report with matching key.
 +     *
 +     * @param reportKey Deletes report with matching key
 +     */
 +    @DELETE
 +    @Path("{reportKey}")
 +    void delete(@NotNull @PathParam("reportKey") Long reportKey);
 +
 +    /**
 +     * Deletes report execution with matching key.
 +     *
 +     * @param executionKey key of execution report to be deleted
 +     */
 +    @DELETE
 +    @Path("executions/{executionKey}")
 +    void deleteExecution(@NotNull @PathParam("executionKey") Long executionKey);
 +
 +    /**
 +     * Executes the report with matching key.
 +     *
 +     * @param reportKey key of report to be executed
 +     * @return report execution result
 +     */
 +    @POST
 +    @Path("{reportKey}/execute")
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    ReportExecTO execute(@NotNull @PathParam("reportKey") Long reportKey);
 +
 +    /**
 +     * Exports the report execution with matching key in the requested format.
 +     *
 +     * @param executionKey key of execution report to be selected
 +     * @param fmt file-format selection
 +     * @return a stream for content download
 +     */
 +    @GET
 +    @Path("executions/{executionKey}/stream")
 +    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    Response exportExecutionResult(@NotNull @PathParam("executionKey") Long executionKey,
 +            @QueryParam("format") ReportExecExportFormat fmt);
 +
 +    /**
-      * List report jobs of the given type
++     * List report jobs of the given type.
 +     *
 +     * @param type of report job
-      * @return List of ReportExecTO
++     * @return list of report jobs of the given type
 +     */
 +    @GET
 +    @Path("jobs")
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-     List<ReportExecTO> list(@MatrixParam("type") JobStatusType type);
++    List<ReportExecTO> listJobs(@MatrixParam("type") JobStatusType type);
 +
 +    /**
-      * Execute a control action on an existing report
++     * Executes an action on an existing report's job.
 +     *
++     * @param reportKey report key
 +     * @param action
-      * @param reportId id of report
 +     */
 +    @POST
-     @Path("{reportId}")
-     void process(@QueryParam("action") JobAction action, @PathParam("reportId") Long reportId);
++    @Path("{reportKey}")
++    void actionJob(@PathParam("reportKey") Long reportKey, @QueryParam("action") JobAction action);
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/22a9e12e/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
----------------------------------------------------------------------
diff --cc common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
index 3d6c3f5,0000000..84b7cbf
mode 100644,000000..100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
@@@ -1,194 -1,0 +1,194 @@@
 +/*
 + * 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.common.rest.api.service;
 +
 +import java.util.List;
 +import javax.validation.constraints.NotNull;
 +import javax.ws.rs.BeanParam;
 +import javax.ws.rs.Consumes;
 +import javax.ws.rs.DELETE;
 +import javax.ws.rs.DefaultValue;
 +import javax.ws.rs.GET;
 +import javax.ws.rs.MatrixParam;
 +import javax.ws.rs.POST;
 +import javax.ws.rs.PUT;
 +import javax.ws.rs.Path;
 +import javax.ws.rs.PathParam;
 +import javax.ws.rs.Produces;
 +import javax.ws.rs.QueryParam;
 +import javax.ws.rs.core.MediaType;
 +import javax.ws.rs.core.Response;
 +import org.apache.cxf.jaxrs.model.wadl.Description;
 +import org.apache.cxf.jaxrs.model.wadl.Descriptions;
 +import org.apache.cxf.jaxrs.model.wadl.DocTarget;
 +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.PagedResult;
 +import org.apache.syncope.common.lib.to.ReportExecTO;
 +import org.apache.syncope.common.lib.to.SchedTaskTO;
 +import org.apache.syncope.common.lib.to.TaskExecTO;
 +import org.apache.syncope.common.lib.types.JobAction;
 +import org.apache.syncope.common.lib.types.JobStatusType;
 +import org.apache.syncope.common.lib.types.TaskType;
 +import org.apache.syncope.common.rest.api.beans.ListQuery;
 +
 +/**
 + * REST operations for tasks.
 + */
 +@Path("tasks")
 +public interface TaskService extends JAXRSService {
 +
 +    /**
 +     * Returns the task matching the given key.
 +     *
 +     * @param taskKey key of task to be read
 +     * @param <T> type of taskTO
 +     * @return task with matching id
 +     */
 +    @GET
 +    @Path("{taskKey}")
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    <T extends AbstractTaskTO> T read(@NotNull @PathParam("taskKey") Long taskKey);
 +
 +    /**
 +     * Returns the task execution with the given id.
 +     *
 +     * @param executionKey key of task execution to be read
 +     * @return task execution with matching Id
 +     */
 +    @GET
 +    @Path("executions/{executionKey}")
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    TaskExecTO readExecution(@NotNull @PathParam("executionKey") Long executionKey);
 +
 +    /**
 +     * Returns a paged list of existing tasks matching type and the given query.
 +     *
 +     * @param taskType type of tasks to be listed
 +     * @param listQuery query conditions
 +     * @param <T> type of taskTO
 +     * @return paged list of existing tasks matching type and the given query
 +     */
 +    @GET
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    <T extends AbstractTaskTO> PagedResult<T> list(
 +            @NotNull @MatrixParam("type") TaskType taskType,
 +            @BeanParam ListQuery listQuery);
 +
 +    /**
 +     * Creates a new task.
 +     *
 +     * @param taskTO task to be created
 +     * @param <T> type of taskTO
 +     * @return <tt>Response</tt> object featuring <tt>Location</tt> header of created task
 +     */
 +    @Descriptions({
 +        @Description(target = DocTarget.RESPONSE, value = "Featuring <tt>Location</tt> header of created task")
 +    })
 +    @POST
 +    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    <T extends SchedTaskTO> Response create(@NotNull T taskTO);
 +
 +    /**
 +     * Updates the task matching the provided key.
 +     *
 +     * @param taskKey key of task to be updated
 +     * @param taskTO updated task to be stored
 +     */
 +    @PUT
 +    @Path("{taskKey}")
 +    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    void update(@NotNull @PathParam("taskKey") Long taskKey, @NotNull AbstractTaskTO taskTO);
 +
 +    /**
 +     * Deletes the task matching the provided key.
 +     *
 +     * @param taskKey key of task to be deleted
 +     */
 +    @DELETE
 +    @Path("{taskKey}")
 +    void delete(@NotNull @PathParam("taskKey") Long taskKey);
 +
 +    /**
 +     * Deletes the task execution matching the provided key.
 +     *
 +     * @param executionKey key of task execution to be deleted
 +     */
 +    @DELETE
 +    @Path("executions/{executionKey}")
 +    void deleteExecution(@NotNull @PathParam("executionKey") Long executionKey);
 +
 +    /**
 +     * Executes the task matching the given id.
 +     *
 +     * @param taskKey key of task to be executed
 +     * @param dryRun if true, task will only be simulated
 +     * @return execution report for the task matching the given id
 +     */
 +    @POST
 +    @Path("{taskKey}/execute")
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    TaskExecTO execute(@NotNull @PathParam("taskKey") Long taskKey,
 +            @QueryParam("dryRun") @DefaultValue("false") boolean dryRun);
 +
 +    /**
 +     * Reports task execution result.
 +     *
 +     * @param executionKey key of task execution being reported
 +     * @param reportExec execution being reported
 +     */
 +    @POST
 +    @Path("executions/{executionKey}/report")
 +    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    void report(@NotNull @PathParam("executionKey") Long executionKey, @NotNull ReportExecTO reportExec);
 +
 +    /**
 +     * Executes the provided bulk action.
 +     *
 +     * @param bulkAction list of task ids against which the bulk action will be performed.
 +     * @return Bulk action result
 +     */
 +    @POST
 +    @Path("bulk")
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 +    BulkActionResult bulk(@NotNull BulkAction bulkAction);
 +
 +    /**
-      * List task jobs of the given type
++     * List task jobs of the given type.
 +     *
 +     * @param type of task job
-      * @return List of TaskExecTO
++     * @return list task jobs of the given type
 +     */
 +    @GET
 +    @Path("jobs")
 +    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-     List<TaskExecTO> list(@MatrixParam("type") JobStatusType type);
++    List<TaskExecTO> listJobs(@MatrixParam("type") JobStatusType type);
 +
 +    /**
-      * Execute a control action on an existing task
++     * Executes an action on an existing task's job.
 +     *
++     * @param taskKey task key
 +     * @param action
-      * @param taskId id of task
 +     */
 +    @POST
-     @Path("{taskId}")
-     void process(@QueryParam("action") JobAction action, @PathParam("taskId") Long taskId);
++    @Path("{taskKey}")
++    void actionJob(@PathParam("taskKey") Long taskKey, @QueryParam("action") JobAction action);
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/22a9e12e/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
----------------------------------------------------------------------
diff --cc core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
index 9f20ee1,0000000..e423174
mode 100644,000000..100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
@@@ -1,155 -1,0 +1,174 @@@
 +/*
 + * 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;
 +
 +import java.util.ArrayList;
 +import java.util.List;
 +import org.apache.syncope.common.lib.AbstractBaseBean;
 +import org.apache.syncope.common.lib.to.AbstractExecTO;
++import org.apache.syncope.common.lib.to.ReportExecTO;
++import org.apache.syncope.common.lib.to.TaskExecTO;
 +import org.apache.syncope.common.lib.types.JobAction;
 +import org.apache.syncope.common.lib.types.JobStatusType;
 +import org.quartz.JobExecutionContext;
 +import org.quartz.JobKey;
 +import org.quartz.Scheduler;
 +import org.quartz.SchedulerException;
 +import org.quartz.Trigger;
 +import org.quartz.impl.matchers.GroupMatcher;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 +
 +abstract class AbstractJobLogic<T extends AbstractBaseBean> extends AbstractTransactionalLogic<T> {
 +
 +    @Autowired
 +    protected SchedulerFactoryBean scheduler;
 +
 +    protected abstract Long getKeyFromJobName(final JobKey jobKey);
 +
-     public <E extends AbstractExecTO> List<E> list(final JobStatusType type, final Class<E> reference) {
-         List<E> jobExecTOs = new ArrayList<E>();
++    private <E extends AbstractExecTO> void setTaskOrReportKey(final E jobExecTO, final Long taskOrReportKey) {
++        if (jobExecTO instanceof TaskExecTO) {
++            ((TaskExecTO) jobExecTO).setTask(taskOrReportKey);
++        } else if (jobExecTO instanceof ReportExecTO) {
++            ((ReportExecTO) jobExecTO).setReport(taskOrReportKey);
++        }
++    }
++
++    public <E extends AbstractExecTO> List<E> listJobs(final JobStatusType type, final Class<E> reference) {
++        List<E> jobExecTOs = new ArrayList<>();
 +
 +        switch (type) {
 +            case ALL:
 +                try {
 +                    for (String groupName : scheduler.getScheduler().getJobGroupNames()) {
-                         for (JobKey jobKey : scheduler.getScheduler().getJobKeys(GroupMatcher.
-                                 jobGroupEquals(groupName))) {
++                        for (JobKey jobKey
++                                : scheduler.getScheduler().getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
 +
-                             Long jobId = getKeyFromJobName(jobKey);
-                             if (jobId != null) {
++                            Long key = getKeyFromJobName(jobKey);
++                            if (key != null) {
 +                                List<? extends Trigger> jobTriggers = scheduler.getScheduler().getTriggersOfJob(jobKey);
-                                 if (jobTriggers.size() > 0) {
++                                if (jobTriggers.isEmpty()) {
++                                    E jobExecTO = reference.newInstance();
++                                    setTaskOrReportKey(jobExecTO, key);
++                                    jobExecTO.setStatus("Not Scheduled");
++
++                                    jobExecTOs.add(jobExecTO);
++                                } else {
 +                                    for (Trigger t : jobTriggers) {
 +                                        E jobExecTO = reference.newInstance();
-                                         jobExecTO.setKey(jobId);
++                                        jobExecTO.setKey(key);
 +                                        jobExecTO.
 +                                                setStatus(scheduler.getScheduler().getTriggerState(t.getKey()).name());
 +                                        jobExecTO.setStartDate(t.getStartTime());
++
 +                                        jobExecTOs.add(jobExecTO);
 +                                    }
-                                 } else {
-                                     E jobExecTO = reference.newInstance();
-                                     jobExecTO.setKey(jobId);
-                                     jobExecTO.setStatus("Not Scheduled");
-                                     jobExecTOs.add(jobExecTO);
 +                                }
 +                            }
 +                        }
 +                    }
-                 } catch (SchedulerException ex) {
-                     LOG.debug("Problems during retrieving all scheduled jobs {}", ex);
-                 } catch (InstantiationException ex) {
-                     LOG.debug("Problems during instantiating {}  {}", reference, ex);
-                 } catch (IllegalAccessException ex) {
-                     LOG.debug("Problems during accessing {}  {}", reference, ex);
++                } catch (SchedulerException e) {
++                    LOG.debug("Problems while retrieving all scheduled jobs", e);
++                } catch (InstantiationException e) {
++                    LOG.debug("Problems while instantiating {}", reference, e);
++                } catch (IllegalAccessException e) {
++                    LOG.debug("Problems while accessing {}", reference, e);
 +                }
 +                break;
++
 +            case RUNNING:
 +                try {
 +                    for (JobExecutionContext jec : scheduler.getScheduler().getCurrentlyExecutingJobs()) {
-                         Long jobId = getKeyFromJobName(jec.getJobDetail().getKey());
-                         if (jobId != null) {
++                        Long key = getKeyFromJobName(jec.getJobDetail().getKey());
++                        if (key != null) {
 +                            E jobExecTO = reference.newInstance();
-                             jobExecTO.setKey(jobId);
-                             jobExecTO.setStatus(scheduler.getScheduler().getTriggerState(jec.getTrigger().getKey()).
-                                     name());
++                            setTaskOrReportKey(jobExecTO, key);
++                            jobExecTO.setStatus(
++                                    scheduler.getScheduler().getTriggerState(jec.getTrigger().getKey()).name());
 +                            jobExecTO.setStartDate(jec.getFireTime());
++
 +                            jobExecTOs.add(jobExecTO);
 +                        }
 +                    }
-                 } catch (SchedulerException ex) {
-                     LOG.debug("Problems during retrieving all currently executing jobs {}", ex);
-                 } catch (InstantiationException ex) {
-                     LOG.debug("Problems during instantiating {}  {}", reference, ex);
-                 } catch (IllegalAccessException ex) {
-                     LOG.debug("Problems during accessing {}  {}", reference, ex);
++                } catch (SchedulerException e) {
++                    LOG.debug("Problems while retrieving all currently executing jobs", e);
++                } catch (InstantiationException e) {
++                    LOG.debug("Problems while instantiating {}", reference, e);
++                } catch (IllegalAccessException e) {
++                    LOG.debug("Problems while accessing {}", reference, e);
 +                }
 +                break;
++
 +            case SCHEDULED:
 +                try {
 +                    for (String groupName : scheduler.getScheduler().getJobGroupNames()) {
-                         for (JobKey jobKey : scheduler.getScheduler().getJobKeys(GroupMatcher.
-                                 jobGroupEquals(groupName))) {
-                             Long jobId = getKeyFromJobName(jobKey);
-                             if (jobId != null) {
++                        for (JobKey jobKey
++                                : scheduler.getScheduler().getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
++
++                            Long key = getKeyFromJobName(jobKey);
++                            if (key != null) {
 +                                List<? extends Trigger> jobTriggers = scheduler.getScheduler().getTriggersOfJob(jobKey);
 +                                for (Trigger t : jobTriggers) {
 +                                    E jobExecTO = reference.newInstance();
-                                     jobExecTO.setKey(jobId);
++                                    setTaskOrReportKey(jobExecTO, key);
 +                                    jobExecTO.setStatus(scheduler.getScheduler().getTriggerState(t.getKey()).name());
 +                                    jobExecTO.setStartDate(t.getStartTime());
++
 +                                    jobExecTOs.add(jobExecTO);
 +                                }
 +                            }
 +                        }
 +                    }
-                 } catch (SchedulerException ex) {
-                     LOG.debug("Problems during retrieving all scheduled jobs {}", ex);
-                 } catch (InstantiationException ex) {
-                     LOG.debug("Problems during instantiating {}  {}", reference, ex);
-                 } catch (IllegalAccessException ex) {
-                     LOG.debug("Problems during accessing {}  {}", reference, ex);
++                } catch (SchedulerException e) {
++                    LOG.debug("Problems while retrieving all scheduled jobs", e);
++                } catch (InstantiationException e) {
++                    LOG.debug("Problems while instantiating {}", reference, e);
++                } catch (IllegalAccessException e) {
++                    LOG.debug("Problems while accessing {}", reference, e);
 +                }
 +                break;
++
 +            default:
 +        }
 +        return jobExecTOs;
 +    }
 +
-     protected void process(final JobAction action, final String jobName) {
- 
++    protected void actionJob(final String jobName, final JobAction action) {
 +        if (jobName != null) {
 +            JobKey jobKey = new JobKey(jobName, Scheduler.DEFAULT_GROUP);
 +            try {
 +                if (scheduler.getScheduler().checkExists(jobKey)) {
 +                    switch (action) {
 +                        case START:
 +                            scheduler.getScheduler().triggerJob(jobKey);
 +                            break;
++
 +                        case STOP:
 +                            scheduler.getScheduler().interrupt(jobKey);
 +                            break;
++
 +                        default:
 +                    }
 +                }
-             } catch (SchedulerException ex) {
-                 LOG.debug("Problems during {} operation on job with id {}", action.toString(), ex);
++            } catch (SchedulerException e) {
++                LOG.debug("Problems during {} operation on job {}", action.toString(), jobName, e);
 +            }
 +        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/22a9e12e/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
----------------------------------------------------------------------
diff --cc core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
index 33e2102,0000000..4c3230c
mode 100644,000000..100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
@@@ -1,424 -1,0 +1,424 @@@
 +/*
 + * 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;
 +
 +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.collections4.CollectionUtils;
 +import org.apache.commons.collections4.PredicateUtils;
 +import org.apache.commons.collections4.Transformer;
 +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.core.persistence.api.dao.NotFoundException;
 +import org.apache.syncope.core.persistence.api.dao.ReportDAO;
 +import org.apache.syncope.core.persistence.api.dao.ReportExecDAO;
 +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 +import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 +import org.apache.syncope.core.persistence.api.entity.Report;
 +import org.apache.syncope.core.persistence.api.entity.ReportExec;
 +import org.apache.syncope.core.provisioning.api.data.ReportDataBinder;
 +import org.apache.syncope.core.provisioning.api.job.JobNamer;
 +import org.apache.syncope.core.logic.init.ImplementationClassNamesLoader;
 +import org.apache.syncope.core.provisioning.api.job.JobInstanceLoader;
 +import org.apache.syncope.core.logic.report.Reportlet;
 +import org.apache.syncope.core.logic.report.ReportletConfClass;
 +import org.apache.syncope.core.logic.report.TextSerializer;
 +import org.apache.syncope.common.lib.CollectionUtils2;
 +import org.apache.syncope.common.lib.to.AbstractExecTO;
 +import org.apache.syncope.common.lib.types.Entitlement;
 +import org.apache.syncope.common.lib.types.JobAction;
 +import org.apache.syncope.common.lib.types.JobStatusType;
 +import org.apache.xmlgraphics.util.MimeConstants;
 +import org.quartz.JobKey;
 +import org.quartz.Scheduler;
 +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.util.ClassUtils;
 +
 +@Component
 +public class ReportLogic extends AbstractJobLogic<ReportTO> {
 +
 +    @Autowired
 +    private ReportDAO reportDAO;
 +
 +    @Autowired
 +    private ReportExecDAO reportExecDAO;
 +
 +    @Autowired
 +    private JobInstanceLoader jobInstanceLoader;
 +
 +    @Autowired
 +    private ReportDataBinder binder;
 +
 +    @Autowired
 +    private EntityFactory entityFactory;
 +
 +    @Autowired
 +    private ImplementationClassNamesLoader classNamesLoader;
 +
 +    @PreAuthorize("hasRole('" + Entitlement.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('" + Entitlement.REPORT_UPDATE + "')")
 +    public ReportTO update(final ReportTO reportTO) {
 +        Report report = reportDAO.find(reportTO.getKey());
 +        if (report == null) {
 +            throw new NotFoundException("Report " + reportTO.getKey());
 +        }
 +
 +        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('" + Entitlement.REPORT_LIST + "')")
 +    public int count() {
 +        return reportDAO.count();
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.REPORT_LIST + "')")
 +    public List<ReportTO> list(final int page, final int size, final List<OrderByClause> orderByClauses) {
 +        return CollectionUtils.collect(reportDAO.findAll(page, size, orderByClauses),
 +                new Transformer<Report, ReportTO>() {
 +
 +                    @Override
 +                    public ReportTO transform(final Report input) {
 +                        return binder.getReportTO(input);
 +                    }
 +                }, new ArrayList<ReportTO>());
 +    }
 +
 +    private Class<? extends ReportletConf> getReportletConfClass(final Class<Reportlet> reportletClass) {
 +        Class<? extends ReportletConf> result = null;
 +
 +        ReportletConfClass annotation = reportletClass.getAnnotation(ReportletConfClass.class);
 +        if (annotation != null) {
 +            result = annotation.value();
 +        }
 +
 +        return result;
 +    }
 +
 +    @SuppressWarnings({ "rawtypes" })
 +    private Set<Class<Reportlet>> getAllReportletClasses() {
 +        return CollectionUtils2.collect(classNamesLoader.getClassNames(ImplementationClassNamesLoader.Type.REPORTLET),
 +                new Transformer<String, Class<Reportlet>>() {
 +
 +                    @SuppressWarnings("unchecked")
 +                    @Override
 +                    public Class<Reportlet> transform(final String className) {
 +                        Class<Reportlet> result = null;
 +                        try {
 +                            Class reportletClass = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
 +                            result = reportletClass;
 +                        } catch (ClassNotFoundException e) {
 +                            LOG.warn("Could not load class {}", className);
 +                        } catch (LinkageError e) {
 +                            LOG.warn("Could not link class {}", className);
 +                        }
 +
 +                        return result;
 +                    }
 +                },
 +                PredicateUtils.notNullPredicate(), new HashSet<Class<Reportlet>>());
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.REPORT_LIST + "')")
 +    public Set<String> getReportletConfClasses() {
 +        return CollectionUtils2.collect(getAllReportletClasses(),
 +                new Transformer<Class<Reportlet>, String>() {
 +
 +                    @Override
 +                    public String transform(final Class<Reportlet> reportletClass) {
 +                        Class<? extends ReportletConf> reportletConfClass = getReportletConfClass(reportletClass);
 +                        return reportletConfClass == null ? null : reportletConfClass.getName();
 +                    }
 +                }, PredicateUtils.notNullPredicate(), new HashSet<String>());
 +    }
 +
 +    public Class<Reportlet> findReportletClassHavingConfClass(final Class<? extends ReportletConf> reportletConfClass) {
 +        Class<Reportlet> result = null;
 +        for (Class<Reportlet> reportletClass : getAllReportletClasses()) {
 +            Class<? extends ReportletConf> found = getReportletConfClass(reportletClass);
 +            if (found != null && found.equals(reportletConfClass)) {
 +                result = reportletClass;
 +            }
 +        }
 +
 +        return result;
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.REPORT_READ + "')")
 +    public ReportTO read(final Long reportKey) {
 +        Report report = reportDAO.find(reportKey);
 +        if (report == null) {
 +            throw new NotFoundException("Report " + reportKey);
 +        }
 +        return binder.getReportTO(report);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.REPORT_READ + "')")
 +    @Transactional(readOnly = true)
 +    public ReportExecTO readExecution(final Long executionKey) {
 +        ReportExec reportExec = reportExecDAO.find(executionKey);
 +        if (reportExec == null) {
 +            throw new NotFoundException("Report execution " + executionKey);
 +        }
 +        return binder.getReportExecTO(reportExec);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.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<>();
 +            pipeline.addComponent(new XMLGenerator(zis));
 +
 +            Map<String, Object> parameters = new HashMap<>();
 +            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('" + Entitlement.REPORT_READ + "')")
 +    public ReportExec getAndCheckReportExec(final Long executionKey) {
 +        ReportExec reportExec = reportExecDAO.find(executionKey);
 +        if (reportExec == null) {
 +            throw new NotFoundException("Report execution " + executionKey);
 +        }
 +        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('" + Entitlement.REPORT_EXECUTE + "')")
 +    public ReportExecTO execute(final Long reportKey) {
 +        Report report = reportDAO.find(reportKey);
 +        if (report == null) {
 +            throw new NotFoundException("Report " + reportKey);
 +        }
 +
 +        try {
 +            jobInstanceLoader.registerJob(report);
 +
 +            scheduler.getScheduler().triggerJob(
 +                    new JobKey(JobNamer.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(reportKey);
 +        result.setStartDate(new Date());
 +        result.setStatus(ReportExecStatus.STARTED.name());
 +        result.setMessage("Job fired; waiting for results...");
 +
 +        return result;
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.REPORT_DELETE + "')")
 +    public ReportTO delete(final Long reportKey) {
 +        Report report = reportDAO.find(reportKey);
 +        if (report == null) {
 +            throw new NotFoundException("Report " + reportKey);
 +        }
 +
 +        ReportTO deletedReport = binder.getReportTO(report);
 +        jobInstanceLoader.unregisterJob(report);
 +        reportDAO.delete(report);
 +        return deletedReport;
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.REPORT_DELETE + "')")
 +    public ReportExecTO deleteExecution(final Long executionKey) {
 +        ReportExec reportExec = reportExecDAO.find(executionKey);
 +        if (reportExec == null) {
 +            throw new NotFoundException("Report execution " + executionKey);
 +        }
 +
 +        ReportExecTO reportExecToDelete = binder.getReportExecTO(reportExec);
 +        reportExecDAO.delete(reportExec);
 +        return reportExecToDelete;
 +    }
 +
 +    @Override
 +    protected ReportTO resolveReference(final Method method, final Object... args)
 +            throws UnresolvedReferenceException {
 +
 +        Long key = null;
 +
 +        if (ArrayUtils.isNotEmpty(args) && ("create".equals(method.getName())
 +                || "update".equals(method.getName())
 +                || "delete".equals(method.getName()))) {
 +            for (int i = 0; key == null && i < args.length; i++) {
 +                if (args[i] instanceof Long) {
 +                    key = (Long) args[i];
 +                } else if (args[i] instanceof ReportTO) {
 +                    key = ((ReportTO) args[i]).getKey();
 +                }
 +            }
 +        }
 +
 +        if ((key != null) && !key.equals(0L)) {
 +            try {
 +                return binder.getReportTO(reportDAO.find(key));
 +            } catch (Throwable ignore) {
 +                LOG.debug("Unresolved reference", ignore);
 +                throw new UnresolvedReferenceException(ignore);
 +            }
 +        }
 +
 +        throw new UnresolvedReferenceException();
 +    }
 +
 +    @Override
 +    @PreAuthorize("hasRole('" + Entitlement.REPORT_LIST + "')")
-     public <E extends AbstractExecTO> List<E> list(final JobStatusType type, final Class<E> reference) {
-         return super.list(type, reference);
++    public <E extends AbstractExecTO> List<E> listJobs(final JobStatusType type, final Class<E> reference) {
++        return super.listJobs(type, reference);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.REPORT_EXECUTE + "')")
-     public void process(final JobAction action, final Long reportKey) {
++    public void actionJob(final Long reportKey, final JobAction action) {
 +        Report report = reportDAO.find(reportKey);
 +        if (report == null) {
 +            throw new NotFoundException("Report " + reportKey);
 +        }
 +        String jobName = JobNamer.getJobName(report);
-         process(action, jobName);
++        actionJob(jobName, action);
 +    }
 +
 +    @Override
 +    protected Long getKeyFromJobName(final JobKey jobKey) {
 +        return JobNamer.getReportKeyFromJobName(jobKey.getName());
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/22a9e12e/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
----------------------------------------------------------------------
diff --cc core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
index e12c2da,0000000..82dc8d1
mode 100644,000000..100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
@@@ -1,363 -1,0 +1,363 @@@
 +/*
 + * 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;
 +
 +import java.lang.reflect.Method;
 +import java.util.ArrayList;
 +import java.util.Date;
 +import java.util.List;
 +import org.apache.commons.collections4.CollectionUtils;
 +import org.apache.commons.collections4.Transformer;
 +import org.apache.commons.lang3.ArrayUtils;
 +import org.apache.syncope.common.lib.SyncopeClientException;
 +import org.apache.syncope.common.lib.to.AbstractExecTO;
 +import org.apache.syncope.common.lib.to.AbstractTaskTO;
 +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.Entitlement;
 +import org.apache.syncope.common.lib.types.JobAction;
 +import org.apache.syncope.common.lib.types.JobStatusType;
 +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.core.persistence.api.dao.NotFoundException;
 +import org.apache.syncope.core.persistence.api.dao.TaskDAO;
 +import org.apache.syncope.core.persistence.api.dao.TaskExecDAO;
 +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 +import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
 +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 +import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
 +import org.apache.syncope.core.persistence.api.entity.task.Task;
 +import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
 +import org.apache.syncope.core.persistence.api.entity.task.TaskUtils;
 +import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
 +import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
 +import org.apache.syncope.core.provisioning.api.job.JobNamer;
 +import org.apache.syncope.core.provisioning.api.job.TaskJob;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 +import org.apache.syncope.core.provisioning.api.job.JobInstanceLoader;
 +import org.apache.syncope.core.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 AbstractJobLogic<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 TaskUtilsFactory taskUtilsFactory;
 +
 +    @PreAuthorize("hasRole('" + Entitlement.TASK_CREATE + "')")
 +    public <T extends SchedTaskTO> T createSchedTask(final T taskTO) {
 +        TaskUtils taskUtils = taskUtilsFactory.getInstance(taskTO);
 +
 +        SchedTask task = binder.createSchedTask(taskTO, taskUtils);
 +        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, taskUtils);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.TASK_UPDATE + "')")
 +    public SyncTaskTO updateSync(final SyncTaskTO taskTO) {
 +        return updateSched(taskTO);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.TASK_UPDATE + "')")
 +    public <T extends SchedTaskTO> T updateSched(final SchedTaskTO taskTO) {
 +        SchedTask task = taskDAO.find(taskTO.getKey());
 +        if (task == null) {
 +            throw new NotFoundException("Task " + taskTO.getKey());
 +        }
 +
 +        TaskUtils taskUtils = taskUtilsFactory.getInstance(task);
 +
 +        binder.updateSchedTask(task, taskTO, taskUtils);
 +        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, taskUtils);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.TASK_LIST + "')")
 +    public int count(final TaskType taskType) {
 +        return taskDAO.count(taskType);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.TASK_LIST + "')")
 +    @SuppressWarnings("unchecked")
 +    public <T extends AbstractTaskTO> List<T> list(final TaskType taskType,
 +            final int page, final int size, final List<OrderByClause> orderByClauses) {
 +
 +        final TaskUtils taskUtilss = taskUtilsFactory.getInstance(taskType);
 +
 +        return CollectionUtils.collect(taskDAO.findAll(page, size, orderByClauses, taskType),
 +                new Transformer<Task, T>() {
 +
 +                    @Override
 +                    public T transform(final Task task) {
 +                        return (T) binder.getTaskTO(task, taskUtilss);
 +                    }
 +                }, new ArrayList<T>());
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.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, taskUtilsFactory.getInstance(task));
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.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('" + Entitlement.TASK_EXECUTE + "')")
 +    public TaskExecTO execute(final Long taskId, final boolean dryRun) {
 +        Task task = taskDAO.find(taskId);
 +        if (task == null) {
 +            throw new NotFoundException("Task " + taskId);
 +        }
 +        TaskUtils taskUtils = taskUtilsFactory.getInstance(task);
 +
 +        TaskExecTO result = null;
 +        switch (taskUtils.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(JobNamer.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('" + Entitlement.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);
 +
 +        TaskUtils taskUtils = taskUtilsFactory.getInstance(exec.getTask());
 +        if (TaskType.PROPAGATION == taskUtils.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: " + taskUtils);
 +        }
 +
 +        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('" + Entitlement.TASK_DELETE + "')")
 +    public <T extends AbstractTaskTO> T delete(final Long taskId) {
 +        Task task = taskDAO.find(taskId);
 +        if (task == null) {
 +            throw new NotFoundException("Task " + taskId);
 +        }
 +        TaskUtils taskUtils = taskUtilsFactory.getInstance(task);
 +
 +        T taskToDelete = binder.getTaskTO(task, taskUtils);
 +
 +        if (TaskType.SCHEDULED == taskUtils.getType()
 +                || TaskType.SYNCHRONIZATION == taskUtils.getType()
 +                || TaskType.PUSH == taskUtils.getType()) {
 +
 +            jobInstanceLoader.unregisterJob(task);
 +        }
 +
 +        taskDAO.delete(task);
 +        return taskToDelete;
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.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;
 +    }
 +
 +    @Override
 +    protected AbstractTaskTO resolveReference(final Method method, final Object... args)
 +            throws UnresolvedReferenceException {
 +
 +        Long key = null;
 +
 +        if (ArrayUtils.isNotEmpty(args)
 +                && !"deleteExecution".equals(method.getName()) && !"readExecution".equals(method.getName())) {
 +
 +            for (int i = 0; key == null && i < args.length; i++) {
 +                if (args[i] instanceof Long) {
 +                    key = (Long) args[i];
 +                } else if (args[i] instanceof AbstractTaskTO) {
 +                    key = ((AbstractTaskTO) args[i]).getKey();
 +                }
 +            }
 +        }
 +
 +        if ((key != null) && !key.equals(0L)) {
 +            try {
 +                final Task task = taskDAO.find(key);
 +                return binder.getTaskTO(task, taskUtilsFactory.getInstance(task));
 +            } catch (Throwable ignore) {
 +                LOG.debug("Unresolved reference", ignore);
 +                throw new UnresolvedReferenceException(ignore);
 +            }
 +        }
 +
 +        throw new UnresolvedReferenceException();
 +    }
 +
 +    @Override
 +    @PreAuthorize("hasRole('" + Entitlement.TASK_LIST + "')")
-     public <E extends AbstractExecTO> List<E> list(final JobStatusType type, final Class<E> reference) {
-         return super.list(type, reference);
++    public <E extends AbstractExecTO> List<E> listJobs(final JobStatusType type, final Class<E> reference) {
++        return super.listJobs(type, reference);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.TASK_EXECUTE + "')")
-     public void process(final JobAction action, final Long taskId) {
-         Task task = taskDAO.find(taskId);
++    public void actionJob(final Long taskKey, final JobAction action) {
++        Task task = taskDAO.find(taskKey);
 +        if (task == null) {
-             throw new NotFoundException("Task " + taskId);
++            throw new NotFoundException("Task " + taskKey);
 +        }
 +        String jobName = JobNamer.getJobName(task);
-         process(action, jobName);
++        actionJob(jobName, action);
 +    }
 +
 +    @Override
 +    protected Long getKeyFromJobName(final JobKey jobKey) {
 +        return JobNamer.getTaskKeyFromJobName(jobKey.getName());
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/22a9e12e/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
----------------------------------------------------------------------
diff --cc core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
index abad867,0000000..53d974e
mode 100644,000000..100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
@@@ -1,475 -1,0 +1,475 @@@
 +/*
 + * 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;
 +
 +import java.lang.reflect.Method;
 +import java.security.AccessControlException;
 +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 org.apache.commons.collections4.CollectionUtils;
 +import org.apache.commons.collections4.Transformer;
 +import org.apache.commons.lang3.ArrayUtils;
 +import org.apache.commons.lang3.tuple.ImmutablePair;
 +import org.apache.commons.lang3.tuple.Pair;
 +import org.apache.syncope.common.lib.SyncopeClientException;
 +import org.apache.syncope.common.lib.mod.AttrMod;
 +import org.apache.syncope.common.lib.mod.StatusMod;
 +import org.apache.syncope.common.lib.mod.UserMod;
 +import org.apache.syncope.common.lib.to.PropagationStatus;
 +import org.apache.syncope.common.lib.to.UserTO;
 +import org.apache.syncope.common.lib.types.ClientExceptionType;
 +import org.apache.syncope.common.lib.types.Entitlement;
 +import org.apache.syncope.common.lib.types.SubjectType;
 +import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 +import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 +import org.apache.syncope.core.persistence.api.dao.SubjectSearchDAO;
 +import org.apache.syncope.core.persistence.api.dao.UserDAO;
 +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 +import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 +import org.apache.syncope.core.persistence.api.entity.group.Group;
 +import org.apache.syncope.core.persistence.api.entity.user.User;
 +import org.apache.syncope.core.provisioning.api.AttributableTransformer;
 +import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
 +import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 +import org.apache.syncope.core.provisioning.java.VirAttrHandler;
 +import org.apache.syncope.core.misc.security.AuthContextUtils;
 +import org.apache.syncope.core.misc.security.UnauthorizedException;
 +import org.apache.syncope.core.misc.serialization.POJOHelper;
 +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 UserLogic extends AbstractSubjectLogic<UserTO, UserMod> {
 +
 +    @Autowired
 +    protected UserDAO userDAO;
 +
 +    @Autowired
 +    protected GroupDAO groupDAO;
 +
 +    @Autowired
 +    protected SubjectSearchDAO searchDAO;
 +
 +    @Autowired
 +    protected UserDataBinder binder;
 +
 +    @Autowired
 +    protected VirAttrHandler virtAttrHandler;
 +
 +    @Autowired
 +    protected PropagationManager propagationManager;
 +
 +    @Autowired
 +    protected PropagationTaskExecutor taskExecutor;
 +
 +    @Autowired
 +    protected AttributableTransformer attrTransformer;
 +
 +    @Autowired
 +    protected UserProvisioningManager provisioningManager;
 +
 +    @Autowired
 +    protected SyncopeLogic syncopeLogic;
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_READ + "')")
 +    public String getUsername(final Long key) {
 +        return binder.getUserTO(key).getUsername();
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_READ + "')")
 +    public Long getKey(final String username) {
 +        return binder.getUserTO(username).getKey();
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_LIST + "')")
 +    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
 +    @Override
 +    public int count(final List<String> realms) {
 +        return userDAO.count(
 +                getEffectiveRealms(AuthContextUtils.getAuthorizations().get(Entitlement.USER_LIST), realms));
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_LIST + "')")
 +    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
 +    @Override
 +    public List<UserTO> list(
 +            final int page, final int size, final List<OrderByClause> orderBy, final List<String> realms) {
 +
 +        return CollectionUtils.collect(userDAO.findAll(
 +                getEffectiveRealms(AuthContextUtils.getAuthorizations().get(Entitlement.USER_LIST), realms),
 +                page, size, orderBy),
 +                new Transformer<User, UserTO>() {
 +
 +                    @Override
 +                    public UserTO transform(final User input) {
 +                        return binder.getUserTO(input);
 +                    }
 +                }, new ArrayList<UserTO>());
 +    }
 +
 +    @PreAuthorize("isAuthenticated()")
 +    @Transactional(readOnly = true)
 +    public Pair<String, UserTO> readSelf() {
 +        return ImmutablePair.of(
 +                POJOHelper.serialize(AuthContextUtils.getAuthorizations()),
 +                binder.getAuthenticatedUserTO());
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_READ + "')")
 +    @Transactional(readOnly = true)
 +    @Override
 +    public UserTO read(final Long key) {
 +        return binder.getUserTO(key);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_SEARCH + "')")
 +    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
 +    @Override
 +    public int searchCount(final SearchCond searchCondition, final List<String> realms) {
 +        return searchDAO.count(
 +                getEffectiveRealms(AuthContextUtils.getAuthorizations().get(Entitlement.USER_SEARCH), realms),
 +                searchCondition, SubjectType.USER);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_SEARCH + "')")
 +    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
 +    @Override
 +    public List<UserTO> search(final SearchCond searchCondition, final int page, final int size,
 +            final List<OrderByClause> orderBy, final List<String> realms) {
 +
 +        List<User> matchingUsers = searchDAO.search(
 +                getEffectiveRealms(AuthContextUtils.getAuthorizations().get(Entitlement.USER_SEARCH), realms),
 +                searchCondition, page, size, orderBy, SubjectType.USER);
 +        return CollectionUtils.collect(matchingUsers, new Transformer<User, UserTO>() {
 +
 +            @Override
 +            public UserTO transform(final User input) {
 +                return binder.getUserTO(input);
 +            }
 +        }, new ArrayList<UserTO>());
 +    }
 +
 +    @PreAuthorize("isAnonymous() or hasRole('" + Entitlement.ANONYMOUS + "')")
 +    public UserTO createSelf(final UserTO userTO, final boolean storePassword) {
 +        return doCreate(userTO, storePassword);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_CREATE + "')")
 +    public UserTO create(final UserTO userTO, final boolean storePassword) {
 +        if (userTO.getRealm() == null) {
 +            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidRealm);
 +            throw sce;
 +        }
 +        Set<String> effectiveRealms = getEffectiveRealms(
 +                AuthContextUtils.getAuthorizations().get(Entitlement.USER_CREATE),
 +                Collections.singleton(userTO.getRealm()));
 +        if (effectiveRealms.isEmpty()) {
 +            throw new UnauthorizedException(SubjectType.USER, null);
 +        }
 +
 +        return doCreate(userTO, storePassword);
 +    }
 +
 +    protected UserTO doCreate(final UserTO userTO, final boolean storePassword) {
 +        // Attributable transformation (if configured)
 +        UserTO actual = attrTransformer.transform(userTO);
 +        LOG.debug("Transformed: {}", actual);
 +
 +        Map.Entry<Long, List<PropagationStatus>> created = provisioningManager.create(actual, storePassword);
 +
 +        final UserTO savedTO = binder.getUserTO(created.getKey());
 +        savedTO.getPropagationStatusTOs().addAll(created.getValue());
 +        return savedTO;
 +    }
 +
 +    @PreAuthorize("isAuthenticated() and not(hasRole('" + Entitlement.ANONYMOUS + "'))")
 +    public UserTO updateSelf(final UserMod userMod) {
 +        UserTO userTO = binder.getAuthenticatedUserTO();
 +
 +        if (userTO.getKey() != userMod.getKey()) {
 +            throw new AccessControlException("Not allowed for user with key " + userMod.getKey());
 +        }
 +
 +        return update(userMod);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_UPDATE + "')")
 +    @Override
 +    public UserTO update(final UserMod userMod) {
 +        // AttributableMod transformation (if configured)
 +        UserMod actual = attrTransformer.transform(userMod);
 +        LOG.debug("Transformed: {}", actual);
 +
 +        // SYNCOPE-501: check if there are memberships to be removed with virtual attributes assigned
 +        boolean removeMemberships = false;
 +        for (Long membershipId : actual.getMembershipsToRemove()) {
 +            if (!virtAttrHandler.fillMembershipVirtual(
 +                    null,
 +                    null,
 +                    membershipId,
 +                    Collections.<String>emptySet(),
 +                    Collections.<AttrMod>emptySet(),
 +                    true).isEmpty()) {
 +
 +                removeMemberships = true;
 +            }
 +        }
 +
 +        Map.Entry<Long, List<PropagationStatus>> updated = provisioningManager.update(actual, removeMemberships);
 +
-         final UserTO updatedTO = binder.getUserTO(updated.getKey());
++        UserTO updatedTO = binder.getUserTO(updated.getKey());
 +        updatedTO.getPropagationStatusTOs().addAll(updated.getValue());
 +        return updatedTO;
 +    }
 +
 +    protected Map.Entry<Long, List<PropagationStatus>> setStatusOnWfAdapter(final User user,
 +            final StatusMod statusMod) {
 +        Map.Entry<Long, List<PropagationStatus>> updated;
 +
 +        switch (statusMod.getType()) {
 +            case SUSPEND:
 +                updated = provisioningManager.suspend(user, statusMod);
 +                break;
 +
 +            case REACTIVATE:
 +                updated = provisioningManager.reactivate(user, statusMod);
 +                break;
 +
 +            case ACTIVATE:
 +            default:
 +                updated = provisioningManager.activate(user, statusMod);
 +                break;
 +
 +        }
 +
 +        return updated;
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_UPDATE + "')")
 +    @Transactional(rollbackFor = { Throwable.class })
 +    public UserTO status(final StatusMod statusMod) {
 +        User user = userDAO.authFetch(statusMod.getKey());
 +
 +        Map.Entry<Long, List<PropagationStatus>> updated = setStatusOnWfAdapter(user, statusMod);
 +        final UserTO savedTO = binder.getUserTO(updated.getKey());
 +        savedTO.getPropagationStatusTOs().addAll(updated.getValue());
 +        return savedTO;
 +    }
 +
 +    @PreAuthorize("isAnonymous() or hasRole('" + Entitlement.ANONYMOUS + "')")
 +    @Transactional
 +    public void requestPasswordReset(final String username, final String securityAnswer) {
 +        if (username == null) {
 +            throw new NotFoundException("Null username");
 +        }
 +
 +        User user = userDAO.find(username);
 +        if (user == null) {
 +            throw new NotFoundException("User " + username);
 +        }
 +
 +        if (syncopeLogic.isPwdResetRequiringSecurityQuestions()
 +                && (securityAnswer == null || !securityAnswer.equals(user.getSecurityAnswer()))) {
 +
 +            throw SyncopeClientException.build(ClientExceptionType.InvalidSecurityAnswer);
 +        }
 +
 +        provisioningManager.requestPasswordReset(user.getKey());
 +    }
 +
 +    @PreAuthorize("isAnonymous() or hasRole('" + Entitlement.ANONYMOUS + "')")
 +    @Transactional
 +    public void confirmPasswordReset(final String token, final String password) {
 +        User user = userDAO.findByToken(token);
 +        if (user == null) {
 +            throw new NotFoundException("User with token " + token);
 +        }
 +        provisioningManager.confirmPasswordReset(user, token, password);
 +    }
 +
 +    @PreAuthorize("isAuthenticated() and not(hasRole('" + Entitlement.ANONYMOUS + "'))")
 +    public UserTO deleteSelf() {
 +        UserTO userTO = binder.getAuthenticatedUserTO();
 +
 +        return delete(userTO.getKey());
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_DELETE + "')")
 +    @Override
 +    public UserTO delete(final Long key) {
 +        List<Group> ownedGroups = groupDAO.findOwnedByUser(key);
 +        if (!ownedGroups.isEmpty()) {
 +            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.GroupOwnership);
 +            sce.getElements().addAll(CollectionUtils.collect(ownedGroups, new Transformer<Group, String>() {
 +
 +                @Override
 +                public String transform(final Group group) {
 +                    return group.getKey() + " " + group.getName();
 +                }
 +            }, new ArrayList<String>()));
 +            throw sce;
 +        }
 +
 +        List<PropagationStatus> statuses = provisioningManager.delete(key);
 +
 +        final UserTO deletedTO;
 +        User deleted = userDAO.find(key);
 +        if (deleted == null) {
 +            deletedTO = new UserTO();
 +            deletedTO.setKey(key);
 +        } else {
 +            deletedTO = binder.getUserTO(key);
 +        }
 +        deletedTO.getPropagationStatusTOs().addAll(statuses);
 +
 +        return deletedTO;
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_UPDATE + "')")
 +    @Transactional(rollbackFor = { Throwable.class })
 +    @Override
 +    public UserTO unlink(final Long key, final Collection<String> resources) {
 +        final UserMod userMod = new UserMod();
 +        userMod.setKey(key);
 +        userMod.getResourcesToRemove().addAll(resources);
 +        Long updatedKey = provisioningManager.unlink(userMod);
 +
 +        return binder.getUserTO(updatedKey);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_UPDATE + "')")
 +    @Transactional(rollbackFor = { Throwable.class })
 +    @Override
 +    public UserTO link(final Long key, final Collection<String> resources) {
 +        final UserMod userMod = new UserMod();
 +        userMod.setKey(key);
 +        userMod.getResourcesToAdd().addAll(resources);
 +        return binder.getUserTO(provisioningManager.link(userMod));
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_UPDATE + "')")
 +    @Transactional(rollbackFor = { Throwable.class })
 +    @Override
 +    public UserTO unassign(final Long key, final Collection<String> resources) {
 +        final UserMod userMod = new UserMod();
 +        userMod.setKey(key);
 +        userMod.getResourcesToRemove().addAll(resources);
 +        return update(userMod);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_UPDATE + "')")
 +    @Transactional(rollbackFor = { Throwable.class })
 +    @Override
 +    public UserTO assign(
 +            final Long key,
 +            final Collection<String> resources,
 +            final boolean changepwd,
 +            final String password) {
 +
 +        final UserMod userMod = new UserMod();
 +        userMod.setKey(key);
 +        userMod.getResourcesToAdd().addAll(resources);
 +
 +        if (changepwd) {
 +            StatusMod statusMod = new StatusMod();
 +            statusMod.setOnSyncope(false);
 +            statusMod.getResourceNames().addAll(resources);
 +            userMod.setPwdPropRequest(statusMod);
 +            userMod.setPassword(password);
 +        }
 +
 +        return update(userMod);
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_UPDATE + "')")
 +    @Transactional(rollbackFor = { Throwable.class })
 +    @Override
 +    public UserTO deprovision(final Long key, final Collection<String> resources) {
 +        final User user = userDAO.authFetch(key);
 +
 +        List<PropagationStatus> statuses = provisioningManager.deprovision(key, resources);
 +
 +        final UserTO updatedUserTO = binder.getUserTO(user);
 +        updatedUserTO.getPropagationStatusTOs().addAll(statuses);
 +        return updatedUserTO;
 +    }
 +
 +    @PreAuthorize("hasRole('" + Entitlement.USER_UPDATE + "')")
 +    @Transactional(readOnly = true)
 +    @Override
 +    public UserTO provision(
 +            final Long key,
 +            final Collection<String> resources,
 +            final boolean changePwd,
 +            final String password) {
 +
 +        final UserTO original = binder.getUserTO(key);
 +
 +        //trick: assign and retrieve propagation statuses ...
 +        original.getPropagationStatusTOs().addAll(
 +                assign(key, resources, changePwd, password).getPropagationStatusTOs());
 +
 +        // .... rollback.
 +        TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
 +        return original;
 +    }
 +
 +    @Override
 +    protected UserTO resolveReference(final Method method, final Object... args) throws UnresolvedReferenceException {
 +        Object key = null;
 +
 +        if (!"confirmPasswordReset".equals(method.getName()) && ArrayUtils.isNotEmpty(args)) {
 +            for (int i = 0; key == null && i < args.length; i++) {
 +                if (args[i] instanceof Long) {
 +                    key = (Long) args[i];
 +                } else if (args[i] instanceof String) {
 +                    key = (String) args[i];
 +                } else if (args[i] instanceof UserTO) {
 +                    key = ((UserTO) args[i]).getKey();
 +                } else if (args[i] instanceof UserMod) {
 +                    key = ((UserMod) args[i]).getKey();
 +                }
 +            }
 +        }
 +
 +        if ((key != null) && !key.equals(0L)) {
 +            try {
 +                return key instanceof Long ? binder.getUserTO((Long) key) : binder.getUserTO((String) key);
 +            } catch (Throwable ignore) {
 +                LOG.debug("Unresolved reference", ignore);
 +                throw new UnresolvedReferenceException(ignore);
 +            }
 +        }
 +
 +        throw new UnresolvedReferenceException();
 +    }
 +}