[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ò <[email protected]> Authored: Fri May 22 12:24:30 2015 +0200 Committer: Francesco Chicchiriccò <[email protected]> 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(); + } +}
