[SYNCOPE-1279] Now providing runtime status updates from running Tasks and Reports
Project: http://git-wip-us.apache.org/repos/asf/syncope/repo Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/772206a4 Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/772206a4 Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/772206a4 Branch: refs/heads/master Commit: 772206a4cb0cadb5ace021e3608d5fcd5b78f505 Parents: 4f6fa1a Author: Francesco Chicchiriccò <ilgro...@apache.org> Authored: Fri Mar 2 11:45:34 2018 +0100 Committer: Francesco Chicchiriccò <ilgro...@apache.org> Committed: Fri Mar 2 12:47:00 2018 +0100 ---------------------------------------------------------------------- .../console/reports/ReportDirectoryPanel.java | 56 ++++ .../console/rest/AnyObjectRestClient.java | 2 - .../client/console/rest/ReportRestClient.java | 4 + .../client/console/rest/TaskRestClient.java | 4 + .../tasks/ProvisioningTaskDirectoryPanel.java | 61 ++++ .../client/console/widgets/JobActionPanel.java | 23 +- .../client/console/widgets/JobWidget.java | 4 +- .../META-INF/resources/css/syncopeConsole.css | 6 +- .../client/console/widgets/JobActionPanel.html | 2 + .../org/apache/syncope/common/lib/to/JobTO.java | 11 + .../rest/api/service/ExecutableService.java | 11 + .../core/logic/AbstractExecutableLogic.java | 2 + .../syncope/core/logic/AbstractJobLogic.java | 63 ++-- .../apache/syncope/core/logic/ReportLogic.java | 25 ++ .../apache/syncope/core/logic/TaskLogic.java | 27 +- .../core/persistence/api/dao/Reportlet.java | 4 +- .../core/provisioning/api/Connector.java | 7 +- .../core/provisioning/api/job/JobDelegate.java | 27 ++ .../api/job/SchedTaskJobDelegate.java | 2 +- .../api/job/report/ReportJobDelegate.java | 27 ++ .../notification/NotificationJobDelegate.java | 31 ++ .../api/pushpull/SyncopePullExecutor.java | 3 + .../provisioning/java/ConnectorFacadeProxy.java | 13 +- .../java/job/AbstractInterruptableJob.java | 21 +- .../java/job/AbstractSchedTaskJobDelegate.java | 12 + .../GroupMemberProvisionTaskJobDelegate.java | 28 +- .../java/job/IdentityRecertification.java | 9 +- .../core/provisioning/java/job/TaskJob.java | 21 +- .../DefaultNotificationJobDelegate.java | 296 +++++++++++++++++++ .../java/job/notification/NotificationJob.java | 7 + .../notification/NotificationJobDelegate.java | 278 ----------------- .../java/job/report/AbstractReportlet.java | 8 +- .../java/job/report/AuditReportlet.java | 16 +- .../job/report/DefaultReportJobDelegate.java | 196 ++++++++++++ .../java/job/report/GroupReportlet.java | 19 +- .../job/report/ReconciliationReportlet.java | 106 +++++-- .../provisioning/java/job/report/ReportJob.java | 7 + .../java/job/report/ReportJobDelegate.java | 177 ----------- .../java/job/report/StaticReportlet.java | 8 +- .../java/job/report/UserReportlet.java | 17 +- .../AbstractPropagationTaskExecutor.java | 11 +- .../pushpull/AbstractPullResultHandler.java | 2 + .../pushpull/DefaultRealmPullResultHandler.java | 2 + .../java/pushpull/PullJobDelegate.java | 40 +++ .../java/pushpull/PushJobDelegate.java | 52 ++++ .../cxf/service/AbstractExecutableService.java | 5 + 46 files changed, 1191 insertions(+), 562 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java index 4d1c1c2..21b1563 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java @@ -34,27 +34,36 @@ import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.panels.DirectoryPanel; import org.apache.syncope.client.console.panels.MultilevelPanel; import org.apache.syncope.client.console.rest.ReportRestClient; +import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType; import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; +import org.apache.syncope.client.console.widgets.JobActionPanel; import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.to.JobTO; import org.apache.syncope.common.lib.to.ReportTO; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; import org.apache.wicket.event.Broadcast; +import org.apache.wicket.event.IEvent; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.markup.html.WebPage; +import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.StringResourceModel; +import org.apache.wicket.util.time.Duration; /** * Reports page. @@ -76,6 +85,17 @@ public abstract class ReportDirectoryPanel modal.size(Modal.Size.Large); initResultTable(); + container.add(new IndicatorAjaxTimerBehavior(Duration.seconds(10)) { + + private static final long serialVersionUID = -4661303265651934868L; + + @Override + protected void onTimer(final AjaxRequestTarget target) { + container.modelChanged(); + target.add(container); + } + }); + startAt = new ReportStartAtTogglePanel(container, pageRef); addInnerObject(startAt); } @@ -105,10 +125,46 @@ public abstract class ReportDirectoryPanel columns.add(new BooleanPropertyColumn<>( new StringResourceModel("active", this), "active", "active")); + columns.add(new AbstractColumn<ReportTO, String>(new Model<>(""), "running") { + + private static final long serialVersionUID = 4209532514416998046L; + + @Override + public void populateItem( + final Item<ICellPopulator<ReportTO>> cellItem, + final String componentId, + final IModel<ReportTO> rowModel) { + + JobTO jobTO = restClient.getJob(rowModel.getObject().getKey()); + JobActionPanel panel = new JobActionPanel( + componentId, jobTO, false, ReportDirectoryPanel.this, pageRef); + MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE, + String.format("%s,%s", + StandardEntitlement.TASK_EXECUTE, + StandardEntitlement.TASK_UPDATE)); + cellItem.add(panel); + } + + @Override + public String getCssClass() { + return "col-xs-1"; + } + }); + return columns; } @Override + public void onEvent(final IEvent<?> event) { + if (event.getPayload() instanceof JobActionPanel.JobActionPayload) { + container.modelChanged(); + JobActionPanel.JobActionPayload.class.cast(event.getPayload()).getTarget().add(container); + } else { + super.onEvent(event); + } + } + + @Override public ActionsPanel<ReportTO> getActions(final IModel<ReportTO> model) { final ActionsPanel<ReportTO> panel = super.getActions(model); http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java index 31cd3a8..a885764 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java @@ -18,8 +18,6 @@ */ package org.apache.syncope.client.console.rest; -import static org.apache.syncope.client.console.rest.BaseRestClient.getService; - import java.util.List; import javax.ws.rs.core.GenericType; import javax.ws.rs.core.Response; http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java index 0c96627..e65ce7e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java @@ -54,6 +54,10 @@ public class ReportRestClient extends BaseRestClient return getService(ReportService.class).list(); } + public JobTO getJob(final String key) { + return getService(ReportService.class).getJob(key); + } + public List<JobTO> listJobs() { return getService(ReportService.class).listJobs(); } http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java index d184085..723ae2e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java @@ -47,6 +47,10 @@ public class TaskRestClient extends BaseRestClient implements ExecutionRestClien private static final long serialVersionUID = 6284485820911028843L; + public JobTO getJob(final String key) { + return getService(TaskService.class).getJob(key); + } + public List<JobTO> listJobs() { return getService(TaskService.class).listJobs(); } http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java index 07c2ef3..f7f0b2e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java @@ -23,18 +23,32 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import org.apache.syncope.client.console.panels.MultilevelPanel; +import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.syncope.client.console.widgets.JobActionPanel; +import org.apache.syncope.common.lib.to.JobTO; import org.apache.syncope.common.lib.to.ProvisioningTaskTO; import org.apache.syncope.common.lib.to.PullTaskTO; import org.apache.syncope.common.lib.to.PushTaskTO; +import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.syncope.common.lib.types.TaskType; import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; +import org.apache.wicket.event.IEvent; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.markup.html.WebPage; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; import org.apache.wicket.model.StringResourceModel; +import org.apache.wicket.util.time.Duration; /** * Tasks page. @@ -63,6 +77,17 @@ public abstract class ProvisioningTaskDirectoryPanel<T extends ProvisioningTaskT // super in order to call the parent implementation super.initResultTable(); + + container.add(new IndicatorAjaxTimerBehavior(Duration.seconds(10)) { + + private static final long serialVersionUID = -4661303265651934868L; + + @Override + protected void onTimer(final AjaxRequestTarget target) { + container.modelChanged(); + target.add(container); + } + }); } @Override @@ -103,9 +128,45 @@ public abstract class ProvisioningTaskDirectoryPanel<T extends ProvisioningTaskT columns.add(new BooleanPropertyColumn<>( new StringResourceModel("active", this), "active", "active")); + columns.add(new AbstractColumn<T, String>(new Model<>(""), "running") { + + private static final long serialVersionUID = -4008579357070833846L; + + @Override + public void populateItem( + final Item<ICellPopulator<T>> cellItem, + final String componentId, + final IModel<T> rowModel) { + + JobTO jobTO = restClient.getJob(rowModel.getObject().getKey()); + JobActionPanel panel = new JobActionPanel( + componentId, jobTO, false, ProvisioningTaskDirectoryPanel.this, pageRef); + MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE, + String.format("%s,%s", + StandardEntitlement.TASK_EXECUTE, + StandardEntitlement.TASK_UPDATE)); + cellItem.add(panel); + } + + @Override + public String getCssClass() { + return "col-xs-1"; + } + }); + return columns; } + @Override + public void onEvent(final IEvent<?> event) { + if (event.getPayload() instanceof JobActionPanel.JobActionPayload) { + container.modelChanged(); + JobActionPanel.JobActionPayload.class.cast(event.getPayload()).getTarget().add(container); + } else { + super.onEvent(event); + } + } + protected class ProvisioningTasksProvider<T extends ProvisioningTaskTO> extends SchedTasksProvider<T> { private static final long serialVersionUID = 4725679400450513556L; http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java index 82665f8..5e03e2d 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java @@ -18,6 +18,9 @@ */ package org.apache.syncope.client.console.widgets; +import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverBehavior; +import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverConfig; +import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig; import java.io.Serializable; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; @@ -30,10 +33,13 @@ import org.apache.syncope.client.console.wicket.ajax.markup.html.IndicatorAjaxLi import org.apache.syncope.client.console.wizards.WizardMgtPanel; import org.apache.syncope.common.lib.to.JobTO; import org.apache.syncope.common.lib.types.JobAction; +import org.apache.wicket.Component; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.event.Broadcast; +import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.model.Model; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,14 +58,21 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> { public JobActionPanel( final String id, final JobTO jobTO, - final JobWidget widget, + final boolean showNotRunning, + final Component container, final PageReference pageRef) { + super(id, true); setOutputMarkupId(true); Fragment controls; if (jobTO.isRunning()) { controls = new Fragment("controls", "runningFragment", this); + controls.add(new Label("status", Model.of()).add(new PopoverBehavior( + Model.<String>of(), + Model.of("<pre>" + (jobTO.getStatus() == null ? StringUtils.EMPTY : jobTO.getStatus()) + "</pre>"), + new PopoverConfig().withAnimation(true).withHoverTrigger().withHtml(true). + withPlacement(TooltipConfig.Placement.left)))); controls.add(new IndicatorAjaxLink<Void>("stop") { private static final long serialVersionUID = -7978723352517770644L; @@ -83,7 +96,7 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> { default: } SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - send(widget, Broadcast.EXACT, new JobActionPayload(target)); + send(container, Broadcast.EXACT, new JobActionPayload(target)); } catch (Exception e) { LOG.error("While stopping {}", jobTO.getRefDesc(), e); SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() @@ -117,7 +130,7 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> { default: } SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - send(widget, Broadcast.EXACT, new JobActionPayload(target)); + send(container, Broadcast.EXACT, new JobActionPayload(target)); } catch (Exception e) { LOG.error("While starting {}", jobTO.getRefDesc(), e); SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() @@ -126,6 +139,10 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> { ((BasePage) getPage()).getNotificationPanel().refresh(target); } }); + if (!showNotRunning) { + controls.setOutputMarkupPlaceholderTag(true); + controls.setVisible(false); + } } addInnerObject(controls); } http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java index 5441b13..8575079 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java @@ -357,9 +357,9 @@ public class JobWidget extends BaseWidget { final IModel<JobTO> rowModel) { JobTO jobTO = rowModel.getObject(); - JobActionPanel panel = new JobActionPanel(componentId, jobTO, JobWidget.this, pageRef); + JobActionPanel panel = new JobActionPanel(componentId, jobTO, true, JobWidget.this, pageRef); MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE, - String.format("%s,%s%s,%s", + String.format("%s,%s,%s,%s", StandardEntitlement.TASK_EXECUTE, StandardEntitlement.REPORT_EXECUTE, StandardEntitlement.TASK_UPDATE, http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css ---------------------------------------------------------------------- diff --git a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css index 294c978..c2dcf2c 100644 --- a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css +++ b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css @@ -879,6 +879,10 @@ li.todoitem a { cursor: default; } +.popover{ + max-width: 100%; +} + #popover:hover { cursor: pointer; } @@ -1157,4 +1161,4 @@ div#inline-actions ul.menu i, div#tablehandling ul.menu i { div#tablehandling ul.menu li a { padding: 0px !important; -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html ---------------------------------------------------------------------- diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html index e94f292..8e31f36 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html +++ b/client/console/src/main/resources/org/apache/syncope/client/console/widgets/JobActionPanel.html @@ -22,6 +22,8 @@ under the License. <wicket:fragment wicket:id="runningFragment"> <i id="actionLink" class="fa fa-refresh fa-spin"></i> + <div wicket:id="status" class="fa fa-binoculars"/> + <a href="#" wicket:id="stop" class="fa fa-stop-circle"></a> </wicket:fragment> <wicket:fragment wicket:id="notRunningFragment"> http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java ---------------------------------------------------------------------- diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java index 2cb5690..cee8db2 100644 --- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java +++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/JobTO.java @@ -42,6 +42,8 @@ public class JobTO extends AbstractBaseBean { private Date start; + private String status; + public JobType getType() { return type; } @@ -93,4 +95,13 @@ public class JobTO extends AbstractBaseBean { ? null : new Date(start.getTime()); } + + public String getStatus() { + return status; + } + + public void setStatus(final String status) { + this.status = status; + } + } http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java ---------------------------------------------------------------------- diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java index 2b09d49..16bc38f 100644 --- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java +++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java @@ -101,6 +101,17 @@ public interface ExecutableService extends JAXRSService { ExecTO execute(@BeanParam ExecuteQuery query); /** + * Returns job (running or scheduled) for the executable matching the given key. + * + * @param key executable key + * @return job (running or scheduled) for the given key + */ + @GET + @Path("jobs/{key}") + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + JobTO getJob(@PathParam("key") String key); + + /** * List jobs (running and / or scheduled). * * @return jobs (running and / or scheduled) http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java index d1b7cb3..6b34bc7 100644 --- a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java @@ -42,6 +42,8 @@ public abstract class AbstractExecutableLogic<T extends AbstractBaseBean> extend public abstract BulkActionResult deleteExecutions( String key, Date startedBefore, Date startedAfter, Date endedBefore, Date endedAfter); + public abstract JobTO getJob(String key); + public abstract List<JobTO> listJobs(); public abstract void actionJob(String key, JobAction action); http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java index a93ae2d..844d353 100644 --- a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java @@ -26,11 +26,14 @@ import org.apache.syncope.common.lib.to.JobTO; import org.apache.syncope.common.lib.types.JobAction; import org.apache.syncope.common.lib.types.JobType; import org.apache.syncope.core.provisioning.api.job.JobManager; +import org.apache.syncope.core.provisioning.java.job.AbstractInterruptableJob; +import org.apache.syncope.core.spring.ApplicationContextProvider; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.impl.matchers.GroupMatcher; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.quartz.SchedulerFactoryBean; @@ -44,32 +47,54 @@ abstract class AbstractJobLogic<T extends AbstractBaseBean> extends AbstractTran protected abstract Triple<JobType, String, String> getReference(final JobKey jobKey); - protected List<JobTO> doListJobs() { - List<JobTO> jobTOs = new ArrayList<>(); + protected JobTO getJobTO(final JobKey jobKey) throws SchedulerException { + JobTO jobTO = null; - try { - for (JobKey jobKey : scheduler.getScheduler(). - getJobKeys(GroupMatcher.jobGroupEquals(Scheduler.DEFAULT_GROUP))) { + Triple<JobType, String, String> reference = getReference(jobKey); + if (reference != null) { + jobTO = new JobTO(); - JobTO jobTO = new JobTO(); + jobTO.setType(reference.getLeft()); + jobTO.setRefKey(reference.getMiddle()); + jobTO.setRefDesc(reference.getRight()); - Triple<JobType, String, String> reference = getReference(jobKey); - if (reference != null) { - jobTOs.add(jobTO); + List<? extends Trigger> jobTriggers = scheduler.getScheduler().getTriggersOfJob(jobKey); + if (jobTriggers.isEmpty()) { + jobTO.setScheduled(false); + } else { + jobTO.setScheduled(true); + jobTO.setStart(jobTriggers.get(0).getStartTime()); + } + + jobTO.setRunning(jobManager.isRunning(jobKey)); - jobTO.setType(reference.getLeft()); - jobTO.setRefKey(reference.getMiddle()); - jobTO.setRefDesc(reference.getRight()); + jobTO.setStatus("UNKNOWN"); + if (jobTO.isRunning()) { + try { + Object job = ApplicationContextProvider.getBeanFactory().getBean(jobKey.getName()); + if (job instanceof AbstractInterruptableJob + && ((AbstractInterruptableJob) job).getDelegate() != null) { - List<? extends Trigger> jobTriggers = scheduler.getScheduler().getTriggersOfJob(jobKey); - if (jobTriggers.isEmpty()) { - jobTO.setScheduled(false); - } else { - jobTO.setScheduled(true); - jobTO.setStart(jobTriggers.get(0).getStartTime()); + jobTO.setStatus(((AbstractInterruptableJob) job).getDelegate().currentStatus()); } + } catch (NoSuchBeanDefinitionException e) { + LOG.warn("Could not find job {} implementation", jobKey, e); + } + } + } + + return jobTO; + } + + protected List<JobTO> doListJobs() { + List<JobTO> jobTOs = new ArrayList<>(); + try { + for (JobKey jobKey : scheduler.getScheduler(). + getJobKeys(GroupMatcher.jobGroupEquals(Scheduler.DEFAULT_GROUP))) { - jobTO.setRunning(jobManager.isRunning(jobKey)); + JobTO jobTO = getJobTO(jobKey); + if (jobTO != null) { + jobTOs.add(jobTO); } } } catch (SchedulerException e) { http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java index 534a537..6a5a442 100644 --- a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java @@ -64,6 +64,7 @@ import org.apache.syncope.core.provisioning.api.data.ReportDataBinder; import org.apache.syncope.core.provisioning.api.job.JobNamer; import org.apache.xmlgraphics.util.MimeConstants; import org.quartz.JobKey; +import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -368,6 +369,30 @@ public class ReportLogic extends AbstractExecutableLogic<ReportTO> { return super.doListJobs(); } + @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_READ + "')") + @Override + public JobTO getJob(final String key) { + Report report = reportDAO.find(key); + if (report == null) { + throw new NotFoundException("Report " + key); + } + + JobTO jobTO = null; + try { + jobTO = getJobTO(JobNamer.getJobKey(report)); + } catch (SchedulerException e) { + LOG.error("Problems while retrieving scheduled job {}", JobNamer.getJobKey(report), e); + + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling); + sce.getElements().add(e.getMessage()); + throw sce; + } + if (jobTO == null) { + throw new NotFoundException("Job for report " + key); + } + return jobTO; + } + @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_EXECUTE + "')") @Override public void actionJob(final String key, final JobAction action) { http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java ---------------------------------------------------------------------- diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java index a9ac3d3..baad7e8 100644 --- a/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java +++ b/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java @@ -55,10 +55,11 @@ import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecu import org.apache.syncope.core.persistence.api.dao.ConfDAO; import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO; import org.apache.syncope.core.persistence.api.dao.NotificationDAO; +import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate; import org.apache.syncope.core.provisioning.java.job.TaskJob; -import org.apache.syncope.core.provisioning.java.job.notification.NotificationJobDelegate; import org.quartz.JobDataMap; import org.quartz.JobKey; +import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -385,6 +386,30 @@ public class TaskLogic extends AbstractExecutableLogic<TaskTO> { return super.doListJobs(); } + @PreAuthorize("hasRole('" + StandardEntitlement.TASK_READ + "')") + @Override + public JobTO getJob(final String key) { + Task task = taskDAO.find(key); + if (task == null) { + throw new NotFoundException("Task " + key); + } + + JobTO jobTO = null; + try { + jobTO = getJobTO(JobNamer.getJobKey(task)); + } catch (SchedulerException e) { + LOG.error("Problems while retrieving scheduled job {}", JobNamer.getJobKey(task), e); + + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling); + sce.getElements().add(e.getMessage()); + throw sce; + } + if (jobTO == null) { + throw new NotFoundException("Job for task " + key); + } + return jobTO; + } + @PreAuthorize("hasRole('" + StandardEntitlement.TASK_EXECUTE + "')") @Override public void actionJob(final String key, final JobAction action) { http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java ---------------------------------------------------------------------- diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java index ab05f4a..2fb2a86 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/Reportlet.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.core.persistence.api.dao; +import java.util.concurrent.atomic.AtomicReference; import org.apache.syncope.common.lib.report.ReportletConf; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; @@ -41,7 +42,8 @@ public interface Reportlet { * Actual data extraction for reporting. * * @param handler SAX content handler for streaming result + * @param status current report status (for job reporting) * @throws SAXException if there is any problem in SAX handling */ - void extract(ContentHandler handler) throws SAXException; + void extract(ContentHandler handler, AtomicReference<String> status) throws SAXException; } http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java ---------------------------------------------------------------------- diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java index 7b84fc5..a0b21cb 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java @@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.api; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; import org.apache.syncope.core.persistence.api.entity.ConnInstance; import org.identityconnectors.framework.common.objects.Attribute; @@ -63,7 +64,7 @@ public interface Connector { ObjectClass objectClass, Set<Attribute> attrs, OperationOptions options, - Boolean[] propagationAttempted); + AtomicReference<Boolean> propagationAttempted); /** * Update user / group on a connector instance. @@ -80,7 +81,7 @@ public interface Connector { Uid uid, Set<Attribute> attrs, OperationOptions options, - Boolean[] propagationAttempted); + AtomicReference<Boolean> propagationAttempted); /** * Delete user / group on a connector instance. @@ -94,7 +95,7 @@ public interface Connector { ObjectClass objectClass, Uid uid, OperationOptions options, - Boolean[] propagationAttempted); + AtomicReference<Boolean> propagationAttempted); /** * Fetches all remote objects (for use during full reconciliation). http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java ---------------------------------------------------------------------- diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java new file mode 100644 index 0000000..3bfa292 --- /dev/null +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.provisioning.api.job; + +/** + * Implementations of this interface will perform the actual operations required to Quartz's {@link org.quartz.Job}. + */ +public interface JobDelegate { + + String currentStatus(); +} http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java ---------------------------------------------------------------------- diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java index a03f36b..bb69b10 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/SchedTaskJobDelegate.java @@ -21,7 +21,7 @@ package org.apache.syncope.core.provisioning.api.job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; -public interface SchedTaskJobDelegate { +public interface SchedTaskJobDelegate extends JobDelegate { void execute(String taskKey, boolean dryRun, JobExecutionContext context) throws JobExecutionException; } http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java ---------------------------------------------------------------------- diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java new file mode 100644 index 0000000..bbf455f --- /dev/null +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/report/ReportJobDelegate.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.provisioning.api.job.report; + +import org.apache.syncope.core.provisioning.api.job.JobDelegate; +import org.quartz.JobExecutionException; + +public interface ReportJobDelegate extends JobDelegate { + + void execute(String reportKey) throws JobExecutionException; +} http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java ---------------------------------------------------------------------- diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java new file mode 100644 index 0000000..3dfcddd --- /dev/null +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationJobDelegate.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.provisioning.api.notification; + +import org.apache.syncope.core.persistence.api.entity.task.NotificationTask; +import org.apache.syncope.core.persistence.api.entity.task.TaskExec; +import org.apache.syncope.core.provisioning.api.job.JobDelegate; +import org.quartz.JobExecutionException; + +public interface NotificationJobDelegate extends JobDelegate { + + TaskExec executeSingle(NotificationTask task); + + void execute() throws JobExecutionException; +} http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java ---------------------------------------------------------------------- diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java index ab02282..39eed32 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopePullExecutor.java @@ -18,10 +18,13 @@ */ package org.apache.syncope.core.provisioning.api.pushpull; +import org.identityconnectors.framework.common.objects.Name; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.SyncToken; public interface SyncopePullExecutor { void setLatestSyncToken(ObjectClass objectClass, SyncToken latestSyncToken); + + void reportHandled(ObjectClass objectClass, Name name); } http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java index 90645b2..523ecc0 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicReference; import org.apache.syncope.common.lib.types.ConnectorCapability; import org.apache.syncope.core.persistence.api.entity.ConnInstance; import org.apache.syncope.core.provisioning.api.ConnIdBundleManager; @@ -162,12 +163,12 @@ public class ConnectorFacadeProxy implements Connector { final ObjectClass objectClass, final Set<Attribute> attrs, final OperationOptions options, - final Boolean[] propagationAttempted) { + final AtomicReference<Boolean> propagationAttempted) { Uid result = null; if (connInstance.getCapabilities().contains(ConnectorCapability.CREATE)) { - propagationAttempted[0] = true; + propagationAttempted.set(true); Future<Uid> future = asyncFacade.create(connector, objectClass, attrs, options); try { @@ -197,12 +198,12 @@ public class ConnectorFacadeProxy implements Connector { final Uid uid, final Set<Attribute> attrs, final OperationOptions options, - final Boolean[] propagationAttempted) { + final AtomicReference<Boolean> propagationAttempted) { Uid result = null; if (connInstance.getCapabilities().contains(ConnectorCapability.UPDATE)) { - propagationAttempted[0] = true; + propagationAttempted.set(true); Future<Uid> future = asyncFacade.update(connector, objectClass, uid, attrs, options); @@ -233,10 +234,10 @@ public class ConnectorFacadeProxy implements Connector { final ObjectClass objectClass, final Uid uid, final OperationOptions options, - final Boolean[] propagationAttempted) { + final AtomicReference<Boolean> propagationAttempted) { if (connInstance.getCapabilities().contains(ConnectorCapability.DELETE)) { - propagationAttempted[0] = true; + propagationAttempted.set(true); Future<Uid> future = asyncFacade.delete(connector, objectClass, uid, options); http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java index 19bbf1e..86a7f49 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java @@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.java.job; import java.util.Date; import java.util.concurrent.atomic.AtomicReference; +import org.apache.syncope.core.provisioning.api.job.JobDelegate; import org.apache.syncope.core.provisioning.api.utils.FormatUtils; import org.apache.syncope.core.provisioning.api.job.JobManager; import org.quartz.DisallowConcurrentExecution; @@ -40,13 +41,25 @@ public abstract class AbstractInterruptableJob implements InterruptableJob { */ private final AtomicReference<Thread> runningThread = new AtomicReference<>(); + private final JobDelegate embeddedDelegate = new JobDelegate() { + + @Override + public String currentStatus() { + return "RUNNING THREAD: " + runningThread.get(); + } + }; + private long interruptMaxRetries = 1; + public JobDelegate getDelegate() { + return embeddedDelegate; + } + @Override public void execute(final JobExecutionContext context) throws JobExecutionException { - this.runningThread.set(Thread.currentThread()); + runningThread.set(Thread.currentThread()); try { - this.interruptMaxRetries = context.getMergedJobDataMap().getLong(JobManager.INTERRUPT_MAX_RETRIES_KEY); + interruptMaxRetries = context.getMergedJobDataMap().getLong(JobManager.INTERRUPT_MAX_RETRIES_KEY); } catch (Exception e) { LOG.debug("Could not set {}, defaults to {}", JobManager.INTERRUPT_MAX_RETRIES_KEY, interruptMaxRetries, e); } @@ -54,7 +67,7 @@ public abstract class AbstractInterruptableJob implements InterruptableJob { @Override public void interrupt() throws UnableToInterruptJobException { - Thread thread = this.runningThread.getAndSet(null); + Thread thread = runningThread.getAndSet(null); if (thread == null) { LOG.warn("Unable to retrieve the thread of the current job execution"); } else { @@ -68,7 +81,7 @@ public abstract class AbstractInterruptableJob implements InterruptableJob { } // if the thread is still alive, it should be available in the next stop if (thread.isAlive()) { - this.runningThread.set(thread); + runningThread.set(thread); } } } http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java index 5fc5405..7103b7b 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java @@ -19,6 +19,7 @@ package org.apache.syncope.core.provisioning.java.job; import java.util.Date; +import java.util.concurrent.atomic.AtomicReference; import org.apache.syncope.common.lib.types.AuditElements; import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2; import org.apache.syncope.core.persistence.api.dao.TaskDAO; @@ -72,6 +73,13 @@ public abstract class AbstractSchedTaskJobDelegate implements SchedTaskJobDelega @Autowired protected AuditManager auditManager; + protected final AtomicReference<String> status = new AtomicReference<>(); + + @Override + public String currentStatus() { + return status.get(); + } + @Transactional @Override public void execute(final String taskKey, final boolean dryRun, final JobExecutionContext context) @@ -90,6 +98,8 @@ public abstract class AbstractSchedTaskJobDelegate implements SchedTaskJobDelega execution.setStart(new Date()); execution.setTask(task); + status.set("Initialization completed"); + AuditElements.Result result; try { @@ -110,6 +120,8 @@ public abstract class AbstractSchedTaskJobDelegate implements SchedTaskJobDelega } task = taskDAO.save(task); + status.set("Done"); + notificationManager.createTasks( AuditElements.EventCategoryType.TASK, this.getClass().getSimpleName(), http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java index 4868bbc..4f1a1eb 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java @@ -82,20 +82,25 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel } result.append("provision\n\n"); + status.set(result.toString()); + MembershipCond membershipCond = new MembershipCond(); membershipCond.setGroup(groupKey); List<User> users = searchDAO.search(SearchCond.getLeafCond(membershipCond), AnyTypeKind.USER); Collection<String> groupResourceKeys = groupDAO.findAllResourceKeys(groupKey); + status.set("About to " + + (actionType == BulkMembersActionType.DEPROVISION ? "de" : "") + "provision " + + users.size() + " users from " + groupResourceKeys); for (User user : users) { List<PropagationStatus> statuses = actionType == BulkMembersActionType.DEPROVISION ? userProvisioningManager.deprovision(user.getKey(), groupResourceKeys, false) : userProvisioningManager.provision(user.getKey(), true, null, groupResourceKeys, false); - for (PropagationStatus status : statuses) { + for (PropagationStatus propagationStatus : statuses) { result.append("User ").append(user.getKey()).append('\t'). - append("Resource ").append(status.getResource()).append('\t'). - append(status.getStatus()); - if (StringUtils.isNotBlank(status.getFailureReason())) { - result.append('\n').append(status.getFailureReason()).append('\n'); + append("Resource ").append(propagationStatus.getResource()).append('\t'). + append(propagationStatus.getStatus()); + if (StringUtils.isNotBlank(propagationStatus.getFailureReason())) { + result.append('\n').append(propagationStatus.getFailureReason()).append('\n'); } result.append("\n"); } @@ -105,17 +110,20 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel membershipCond = new MembershipCond(); membershipCond.setGroup(groupKey); List<AnyObject> anyObjects = searchDAO.search(SearchCond.getLeafCond(membershipCond), AnyTypeKind.ANY_OBJECT); + status.set("About to " + + (actionType == BulkMembersActionType.DEPROVISION ? "de" : "") + "provision " + + anyObjects.size() + " any objects from " + groupResourceKeys); for (AnyObject anyObject : anyObjects) { List<PropagationStatus> statuses = actionType == BulkMembersActionType.DEPROVISION ? anyObjectProvisioningManager.deprovision(anyObject.getKey(), groupResourceKeys, false) : anyObjectProvisioningManager.provision(anyObject.getKey(), groupResourceKeys, false); - for (PropagationStatus status : statuses) { + for (PropagationStatus propagationStatus : statuses) { result.append(anyObject.getType().getKey()).append(' ').append(anyObject.getKey()).append('\t'). - append("Resource ").append(status.getResource()).append('\t'). - append(status.getStatus()); - if (StringUtils.isNotBlank(status.getFailureReason())) { - result.append('\n').append(status.getFailureReason()).append('\n'); + append("Resource ").append(propagationStatus.getResource()).append('\t'). + append(propagationStatus.getStatus()); + if (StringUtils.isNotBlank(propagationStatus.getFailureReason())) { + result.append('\n').append(propagationStatus.getFailureReason()).append('\n'); } result.append("\n"); } http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java index f9d310f..60a1956 100755 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java @@ -89,8 +89,15 @@ public class IdentityRecertification extends AbstractSchedTaskJobDelegate { return "DRY RUN"; } + int total = userDAO.count(); + int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1; + + status.set("Processing " + total + " users in " + pages + " pages"); + long now = System.currentTimeMillis(); - for (int page = 1; page <= (userDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) { + for (int page = 1; page <= pages; page++) { + status.set("Processing " + total + " users: page " + page + " of " + pages); + for (User user : userDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE)) { LOG.debug("Processing user: {}", user.getUsername()); http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java index 6f926cf..c2af5e5 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java @@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.java.job; import org.apache.syncope.core.persistence.api.dao.ImplementationDAO; import org.apache.syncope.core.persistence.api.entity.Implementation; +import org.apache.syncope.core.provisioning.api.job.JobDelegate; import org.apache.syncope.core.spring.security.AuthContextUtils; import org.apache.syncope.core.spring.ApplicationContextProvider; import org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate; @@ -53,6 +54,8 @@ public class TaskJob extends AbstractInterruptableJob { */ private String taskKey; + private SchedTaskJobDelegate delegate; + /** * Task key setter. * @@ -63,6 +66,11 @@ public class TaskJob extends AbstractInterruptableJob { } @Override + public JobDelegate getDelegate() { + return delegate; + } + + @Override public void execute(final JobExecutionContext context) throws JobExecutionException { super.execute(context); @@ -72,16 +80,17 @@ public class TaskJob extends AbstractInterruptableJob { try { ImplementationDAO implementationDAO = ApplicationContextProvider.getApplicationContext().getBean(ImplementationDAO.class); - Implementation taskJobDelegate = implementationDAO.find( + Implementation implementation = implementationDAO.find( context.getMergedJobDataMap().getString(DELEGATE_IMPLEMENTATION)); - if (taskJobDelegate == null) { + if (implementation == null) { LOG.error("Could not find Implementation '{}', aborting", context.getMergedJobDataMap().getString(DELEGATE_IMPLEMENTATION)); } else { - ImplementationManager.<SchedTaskJobDelegate>build(taskJobDelegate). - execute(taskKey, - context.getMergedJobDataMap().getBoolean(DRY_RUN_JOBDETAIL_KEY), - context); + delegate = ImplementationManager.<SchedTaskJobDelegate>build(implementation); + delegate.execute( + taskKey, + context.getMergedJobDataMap().getBoolean(DRY_RUN_JOBDETAIL_KEY), + context); } } catch (Exception e) { LOG.error("While executing task {}", taskKey, e); http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java new file mode 100644 index 0000000..7ab218b --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java @@ -0,0 +1,296 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.provisioning.java.job.notification; + +import java.io.PrintStream; +import java.util.Date; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicReference; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.LogOutputStream; +import org.apache.syncope.common.lib.PropertyUtils; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.TaskType; +import org.apache.syncope.common.lib.types.TraceLevel; +import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2; +import org.apache.syncope.core.persistence.api.dao.TaskDAO; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.task.NotificationTask; +import org.apache.syncope.core.persistence.api.entity.task.TaskExec; +import org.apache.syncope.core.provisioning.api.AuditManager; +import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate; +import org.apache.syncope.core.provisioning.api.notification.NotificationManager; +import org.apache.syncope.core.spring.security.Encryptor; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class DefaultNotificationJobDelegate implements InitializingBean, NotificationJobDelegate { + + private static final Logger LOG = LoggerFactory.getLogger(NotificationJobDelegate.class); + + @Autowired + private TaskDAO taskDAO; + + @Autowired + private JavaMailSender mailSender; + + @Autowired + private EntityFactory entityFactory; + + @Autowired + private AuditManager auditManager; + + @Autowired + private NotificationManager notificationManager; + + private final AtomicReference<String> status = new AtomicReference<>(); + + @Override + public void afterPropertiesSet() throws Exception { + if (mailSender instanceof JavaMailSenderImpl) { + JavaMailSenderImpl javaMailSender = (JavaMailSenderImpl) mailSender; + + Properties javaMailProperties = javaMailSender.getJavaMailProperties(); + + Properties props = PropertyUtils.read(Encryptor.class, "mail.properties", "conf.directory").getLeft(); + for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) { + String prop = (String) e.nextElement(); + if (prop.startsWith("mail.smtp.")) { + javaMailProperties.setProperty(prop, props.getProperty(prop)); + } + } + + if (StringUtils.isNotBlank(javaMailSender.getUsername())) { + javaMailProperties.setProperty("mail.smtp.auth", "true"); + } + + javaMailSender.setJavaMailProperties(javaMailProperties); + + String mailDebug = props.getProperty("mail.debug", "false"); + if (BooleanUtils.toBoolean(mailDebug)) { + Session session = javaMailSender.getSession(); + session.setDebug(true); + session.setDebugOut(new PrintStream(new LogOutputStream(LOG))); + } + } + } + + @Override + public String currentStatus() { + return status.get(); + } + + @Transactional + @Override + public TaskExec executeSingle(final NotificationTask task) { + TaskExec execution = entityFactory.newEntity(TaskExec.class); + execution.setTask(task); + execution.setStart(new Date()); + + boolean retryPossible = true; + + if (StringUtils.isBlank(task.getSubject()) || task.getRecipients().isEmpty() + || StringUtils.isBlank(task.getHtmlBody()) || StringUtils.isBlank(task.getTextBody())) { + + String message = "Could not fetch all required information for sending e-mails:\n" + + task.getRecipients() + "\n" + + task.getSender() + "\n" + + task.getSubject() + "\n" + + task.getHtmlBody() + "\n" + + task.getTextBody(); + LOG.error(message); + + execution.setStatus(NotificationJob.Status.NOT_SENT.name()); + retryPossible = false; + + if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) { + execution.setMessage(message); + } + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("About to send e-mails:\n" + + task.getRecipients() + "\n" + + task.getSender() + "\n" + + task.getSubject() + "\n" + + task.getHtmlBody() + "\n" + + task.getTextBody() + "\n"); + } + + status.set("Sending notifications to " + task.getRecipients()); + + for (String to : task.getRecipients()) { + try { + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setTo(to); + helper.setFrom(task.getSender()); + helper.setSubject(task.getSubject()); + helper.setText(task.getTextBody(), task.getHtmlBody()); + + mailSender.send(message); + + execution.setStatus(NotificationJob.Status.SENT.name()); + + StringBuilder report = new StringBuilder(); + switch (task.getTraceLevel()) { + case ALL: + report.append("FROM: ").append(task.getSender()).append('\n'). + append("TO: ").append(to).append('\n'). + append("SUBJECT: ").append(task.getSubject()).append('\n').append('\n'). + append(task.getTextBody()).append('\n').append('\n'). + append(task.getHtmlBody()).append('\n'); + break; + + case SUMMARY: + report.append("E-mail sent to ").append(to).append('\n'); + break; + + case FAILURES: + case NONE: + default: + } + if (report.length() > 0) { + execution.setMessage(report.toString()); + } + + notificationManager.createTasks( + AuditElements.EventCategoryType.TASK, + "notification", + null, + "send", + AuditElements.Result.SUCCESS, + null, + null, + task, + "Successfully sent notification to " + to); + } catch (Exception e) { + LOG.error("Could not send e-mail", e); + + execution.setStatus(NotificationJob.Status.NOT_SENT.name()); + if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) { + execution.setMessage(ExceptionUtils2.getFullStackTrace(e)); + } + + notificationManager.createTasks( + AuditElements.EventCategoryType.TASK, + "notification", + null, + "send", + AuditElements.Result.FAILURE, + null, + null, + task, + "Could not send notification to " + to, e); + } + + execution.setEnd(new Date()); + } + } + + if (hasToBeRegistered(execution)) { + execution = notificationManager.storeExec(execution); + if (retryPossible + && (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT)) { + + handleRetries(execution); + } + } else { + notificationManager.setTaskExecuted(execution.getTask().getKey(), true); + } + + return execution; + } + + @Transactional + @Override + public void execute() throws JobExecutionException { + List<NotificationTask> tasks = taskDAO.<NotificationTask>findToExec(TaskType.NOTIFICATION); + + status.set("Sending out " + tasks.size() + " notifications"); + + for (NotificationTask task : tasks) { + LOG.debug("Found notification task {} to be executed: starting...", task); + executeSingle(task); + LOG.debug("Notification task {} executed", task); + } + } + + private boolean hasToBeRegistered(final TaskExec execution) { + NotificationTask task = (NotificationTask) execution.getTask(); + + // True if either failed and failures have to be registered, or if ALL + // has to be registered. + return (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT + && task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) + || task.getTraceLevel() == TraceLevel.ALL; + } + + private void handleRetries(final TaskExec execution) { + if (notificationManager.getMaxRetries() <= 0) { + return; + } + + long failedExecutionsCount = notificationManager.countExecutionsWithStatus( + execution.getTask().getKey(), NotificationJob.Status.NOT_SENT.name()); + + if (failedExecutionsCount <= notificationManager.getMaxRetries()) { + LOG.debug("Execution of notification task {} will be retried [{}/{}]", + execution.getTask(), failedExecutionsCount, notificationManager.getMaxRetries()); + notificationManager.setTaskExecuted(execution.getTask().getKey(), false); + + auditManager.audit( + AuditElements.EventCategoryType.TASK, + "notification", + null, + "retry", + AuditElements.Result.SUCCESS, + null, + null, + execution, + "Notification task " + execution.getTask().getKey() + " will be retried"); + } else { + LOG.error("Maximum number of retries reached for task {} - giving up", execution.getTask()); + + auditManager.audit( + AuditElements.EventCategoryType.TASK, + "notification", + null, + "retry", + AuditElements.Result.FAILURE, + null, + null, + execution, + "Giving up retries on notification task " + execution.getTask().getKey()); + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/772206a4/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java index 13d45b0..0137e8f 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java @@ -20,6 +20,8 @@ package org.apache.syncope.core.provisioning.java.job.notification; import org.apache.syncope.core.spring.security.AuthContextUtils; import org.apache.syncope.core.persistence.api.DomainsHolder; +import org.apache.syncope.core.provisioning.api.job.JobDelegate; +import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate; import org.apache.syncope.core.provisioning.java.job.AbstractInterruptableJob; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; @@ -54,6 +56,11 @@ public class NotificationJob extends AbstractInterruptableJob { private NotificationJobDelegate delegate; @Override + public JobDelegate getDelegate() { + return delegate; + } + + @Override public void execute(final JobExecutionContext context) throws JobExecutionException { super.execute(context);