//
// 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.
//
=== Commands

A Command is defined via an <<implementations,Implementation>> of type `COMMAND`, providing a Java or Groovy class
for the 
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/api/Command.java[Command^],
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/4_0_X/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/api/Command.java[Command^],
endif::[]
interface, designed to optionally take parameters.

The typical use case is to encapsulate, in a single logical unit, the equivalent of two or more <<rest,REST>> calls.

Once defined, Commands can be executed via dedicated REST endpoints, or via <<engagements,Console UI>>.

=== Tasks

Tasks control the effective operations that are ongoing in the <<core>>.

Whilst tasks define what and how to perform, they are supposed to be run by some entity (depending on the actual task
type, see below for details); their execution result can be saved for later examination.

[[tasks-propagation]]
==== Propagation

A propagation task encapsulates all the information that is required - according to the defined <<mapping,mapping>> - to create,
update or delete a given User, Group or Any Object, to / from a certain Identity Store:

* operation - `CREATE`, `UPDATE` or `DELETE`
* connObjectKey - value for ConnId 
http://connid.tirasa.net/apidocs/1.6/org/identityconnectors/framework/common/objects/Uid.html[unique identifier^]
on the Identity Store
* oldConnObjectKey - the former unique identifier on the Identity Store: bears value only during updates involving the
unique identifier
* attributes - set of ConnId
http://connid.tirasa.net/apidocs/1.6/org/identityconnectors/framework/common/objects/Attribute.html[attributes^] built
upon internal identity data and configured mapping
* resource - related <<external-resources,external resource>>
* objectClass - ConnId
http://connid.tirasa.net/apidocs/1.6/org/identityconnectors/framework/common/objects/ObjectClass.html[object class^]
* entity - reference to the internal identity: User, Group or Any Object

[NOTE]
====
Propagation tasks are automatically generated via the configured
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java[PropagationManager^],
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/4_0_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java[PropagationManager^],
endif::[]
executed (by default) via the
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java[PriorityPropagationTaskExecutor^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/4_0_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java[PriorityPropagationTaskExecutor^]
endif::[]
during the <<propagation,propagation>> process, and are permanently saved - for later re-execution or for examining
the execution details - depending on the trace levels set on the related
<<external-resource-details,external resource>>.

Automatic retry in case of failure can be configured by mean of a <<policies-propagation,propagation policy>>, for the
related external resource.
====

[[tasks-pull]]
==== Pull

Pull tasks are required to define and trigger the <<provisioning-pull,pull>> process from Identity Stores.

When defining a pull task, the following information must be provided:

* related <<external-resources,external resource>>
* chosen <<pull-mode,pull mode>>
* destination <<realms,Realm>> - where entities selected for creation are going to be placed
* whether creation, update or deletion on internal storage are allowed or not
* whether <<remediation,remediation>> is enabled
* whether to synchronize the status information from the related identity store
* selected <<provisioning-pull,matching and unmatching rules>>
* optional <<inboundactions,inbound action(s)>>
* <<pull-templates,entity templates>>
* scheduling information:
** when to start
** https://docs.spring.io/spring-framework/reference/6.2/integration/scheduling.html#scheduling-cron-expression[cron expression^]

[NOTE]
====
Pull tasks are executed, either upon request or due to a schedule, via the
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java[PullJobDelegate^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/4_0_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java[PullJobDelegate^]
endif::[]
during the <<provisioning-pull,pull>> process, and are permanently saved - for later re-execution or for examining
the execution details - depending on the trace level set on the related
<<external-resource-details,external resource>>.
====

[[dryrun]]
[TIP]
.DryRun
====
It is possible to simulate the execution of a pull (or push) task without performing any actual modification by
selecting the _DryRun_ option. The execution results will be still available for examination.
====

[[concurrent-tasks-pull]]
[TIP]
.Concurrent Pull Task Executions
====
By default, pull tasks are set to accept and sequentially process the objects received from the configured External
Resource; it is also possible to configure a pull task to work on several objects at once in order to speed up the
overall execution time.
====

[[tasks-livesync]]
==== Live Sync

Live sync tasks are required to define and trigger the <<provisioning-livesync,live sync>> process from Identity Stores.

When defining a live sync task, the following information must be provided:

* related <<external-resources,external resource>>
* destination <<realms,Realm>> - where entities selected for creation are going to be placed
* whether creation, update or deletion on internal storage are allowed or not
* whether <<remediation,remediation>> is enabled
* whether to synchronize the status information from the related identity store
* selected <<provisioning-livesync,live sync delta mapper>>
* selected <<provisioning-pull,matching and unmatching rules>>
* optional <<inboundactions,inbound action(s)>>
* <<pull-templates,entity templates>>

[NOTE]
====
Live sync tasks are executed via the
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LiveSyncJobDelegate.java[LiveSyncJobDelegate^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/4_0_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LiveSyncJobDelegate.java[LiveSyncJobDelegate^]
endif::[]
during the <<provisioning-livesync,live sync>> process; the execution results are permanently saved - for examining the
execution details - depending on the trace level set on the related <<external-resource-details,external resource>>.
====

[[concurrent-tasks-livesync]]
[TIP]
.Concurrent Live Sync Task Executions
====
By default, live sync tasks are set to accept and sequentially process the objects received from the configured External
Resource; it is also possible to configure a live sync task to work on several objects at once in order to speed up the
overall execution time.
====

[[tasks-push]]
==== Push

Push tasks are required to define and trigger the <<provisioning-push,push>> process to Identity Stores.

When defining a push task, the following information must be provided:

* related <<external-resources,external resource>>
* source <<realms,Realm>> - where entities to push will be read from
* filter information for selecting which internal entities will be pushed onto the identity store
* whether creation, update or deletion on the identity store are allowed or not
* whether to synchronize the status information with internal storage
* selected <<provisioning-push,matching and unmatching rules>>
* optional <<pushactions,push action(s)>>
* scheduling information:
** when to start
** https://docs.spring.io/spring-framework/reference/6.2/integration/scheduling.html#scheduling-cron-expression[cron expression^]

[NOTE]
====
Push tasks are executed, either upon request or due to a schedule, via the
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java[PushJobDelegate^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/4_0_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java[PushJobDelegate^]
endif::[]
during the <<provisioning-push,push>> process, and are permanently saved - for later re-execution or for examining
the execution details - depending on the trace level set on the related
<<external-resource-details,external resource>>.
====

[[concurrent-tasks-push]]
[TIP]
.Concurrent Push Task Executions
====
By default, push tasks are set to sequentially send items to the configured External Resource; it is also possible to
configure a push task to work on several objects at once in order to speed up the overall execution time.
====

[[tasks-notification]]
==== Notification

A notification task encapsulates all the information that is required to send out a notification e-mail, according to the
specification provided in a given <<notifications,notification>>:

* entity - reference to the internal identity - User, Group or Any Object - the notification task refers to
* sender e-mail address
* e-mail subject
* effective e-mail recipient(s)
* e-mail body as plaintext and / or HTML

[NOTE]
====
Notification tasks are automatically generated via the
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java[NotificationManager^],
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/4_0_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java[NotificationManager^],
endif::[]
executed via the
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java[NotificationJob^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/4_0_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJob.java[NotificationJob^]
endif::[]
and are permanently saved - for later re-execution or for examining the execution details - depending on the trace level
 set on the related <<notifications,notification>>.
====

[[tasks-macro]]
==== Macros

Macro tasks are meant to group one or more <<commands>> into a given execution sequence, alongside with
arguments required to run, with option to define an input form to drive user interaction.

When defining a macro task, the following information must be provided:

* commands to run with their args, either statically defined or mapped to form properties by <<jexl,JEXL>> expressions
* <<realms,Realm>> for <<delegated-administration,delegated administration>> to restrict the set of users entitled to
list, update or execute the given macro task
* scheduling information:
** when to start
** https://docs.spring.io/spring-framework/reference/6.2/integration/scheduling.html#scheduling-cron-expression[cron expression^]

===== MacroActions

Macro task execution can be decorated with custom logic to be invoked around task execution, by associating
macro tasks to a given <<implementations,Implementation>> of the
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/api/MacroActions.java[MacroActions^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/4_0_X/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/api/MacroActions.java[MacroActions^]
endif::[]
interface.

.Macro task with input form
====
Let's assume there are two `Command` instances defined, alongside with their arguments:

. `NewPrinterCommand` which creates a new Realm and an AnyObject instance of type `PRINTER` beloging to it
. `BinaryCommand` which simply logs about the received argument

[source,java]
----
public class NewPrinterCommandArgs extends CommandArgs {

    @NotEmpty
    @Schema(description = "parent realm", example = "/even/two", defaultValue = "/",
            requiredMode = Schema.RequiredMode.REQUIRED)
    private String parentRealm = "/";

    @NotEmpty
    @Schema(description = "new realm name", example = "realm123", 
            requiredMode = Schema.RequiredMode.REQUIRED)
    private String realmName;

    @NotEmpty
    @Schema(description = "printer name", example = "printer123", 
            requiredMode = Schema.RequiredMode.REQUIRED)
    private String printerName;

    // getter and setter methods omitted
}
----

[source,java]
----
public class NewPrinterCommand implements Command<NewPrinterCommandArgs> {

    private static final Logger LOG = LoggerFactory.getLogger(NewPrinterCommand.class);

    @Autowired
    private RealmLogic realmLogic;

    @Autowired
    private AnyObjectLogic anyObjectLogic;

    private Optional<RealmTO> getRealm(final String fullPath) {
        return realmLogic.search(null, Set.of(fullPath), Pageable.unpaged()).get().
                filter(realm -> fullPath.equals(realm.getFullPath())).findFirst();
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    public Result run(final TestCommandArgs args) {
        // 1. create new Realm
        RealmTO realm = new RealmTO();
        realm.setName(args.getRealmName());
        realm.setParent(getRealm(args.getParentRealm()).map(RealmTO::getKey).orElse(null));
        realm = realmLogic.create(args.getParentRealm(), realm).getEntity();
        LOG.info("Realm created: {}", realm.getFullPath());

        // 2. create new PRINTER
        AnyObjectTO anyObject = anyObjectLogic.create(new AnyObjectCR.Builder(
                        realm.getFullPath(), "PRINTER", args.getPrinterName()).
                        plainAttr(new Attr.Builder("location").value("location").build()).
                        build(),
                false).getEntity();
        LOG.info("PRINTER created: {}", anyObject.getName());

        return new Result(
                "Realm created: " + realm.getFullPath() 
                + "; PRINTER created: " + anyObject.getName(),
                Map.of("realm", realm.getKey(), "PRINTER", anyObject.getKey()));
    }
}
----

[source,java]
----
public class BinaryCommandArgs extends CommandArgs {

    private static final long serialVersionUID = -8257974017887359696L;

    private String binaryParam;

    // getter and setter method omitted
}
----

[source,java]
----
public class BinaryCommand implements Command<BinaryCommandArgs> {

    private static final Logger LOG = LoggerFactory.getLogger(BinaryCommand.class);

    private static final Tika TIKA = new Tika();

    static {
        TIKA.setMaxStringLength(-1);
    }

    @Override
    public Result run(final BinaryCommandArgs args) {
        String base64 = args.getBinaryParam();
        LOG.info("Input value received: {}", base64);

        byte[] binaryValue = Base64.getDecoder().decode(base64);
        LOG.info("Byte array with length {} and mime type {}", 
                 binaryValue.length, TIKA.detect(binaryValue));

        return new Result("SUCCESS", Map.of());
    }
}
----

Let's also assume that the following `MacroActions` instance is available, showing how to populate dropdown values,
how to perform some example validation and also allowing to alter the arguments value before the related command is
executed:

[source,java]
----
public class SampleMacroActions implements MacroActions {

    @Autowired
    private RealmDAO realmDAO;

    @Autowired
    private RealmSearchDAO realmSearchDAO;

    @Transactional(readOnly = true)
    @Override
    public Map<String, String> getDropdownValues(final String formProperty) {
        return realmSearchDAO.findChildren(realmDAO.getRoot()).stream().
                collect(Collectors.toMap(Realm::getFullPath, Realm::getName));
    }

    @Override
    public void validate(final SyncopeForm form, final Map<String, Object> vars) 
        throws ValidationException {

        Object binaryValue = vars.get("binaryField");
        if (!(binaryValue instanceof byte[])) {
            throw new ValidationException(
              "Expected byte[], found " + binaryValue.getClass().getName());
        }
    }

    @Override
    public void beforeCommand(final Command<CommandArgs> command, final CommandArgs args) {
        if (args instanceof BinaryCommandArgs binaryCommandArgs) {
            // option to alter binaryCommandArgs before the related command is executed
        }
    }
}
----

It is now possible to define the following Macro task (details are simplified to increase readability):

[source,json]
----
    {
      "key": "019a35af-1a5a-75d1-920c-7f388696be0d",
      "cronExpression": null,
      "jobDelegate": "MacroJobDelegate",
      "name": "BinaryMacro",
      "realm": "/",
      "formPropertyDefs": [
        {
          "name": "parent",  // <1>
          "type": "Dropdown",
          "readable": true,
          "writable": true,
          "required": true
        },
        {
          "name": "realm", // <2>
          "type": "String",
          "readable": true,
          "writable": true,
          "required": true
        },
        {
          "name": "binaryField", // <3>
          "type": "Binary",
          "readable": true,
          "writable": true,
          "required": true,
          "mimeType": "application/pdf"
        }
      ],
      "commands": [
        {
          "key": "NewPrinterCommand",
          "args": {
            "_class": "org.apache.syncope.common.lib.command.NewPrinterCommandArgs",
            "parentRealm": "${parent}",  // <4>
            "realmName": "${realm}"  // <5>
          }
        },
        {
          "key": "BinaryCommand",
          "args": {
            "_class": "org.apache.syncope.common.lib.command.BinaryCommandArgs",
            "binaryParam": "${syncope:base64Encode(binaryField)}"  // <6>
          }
        }
      ],
      "continueOnError": false,
      "saveExecs": true,
      "macroActions": "SampleMacroActions"
    }
----
<1> dropdown form property whose values are generated by `SampleMacroActions#getDropdownValues`
<2> string form property
<3> binary form property expecting a PDF input file
<4> binds the `parentRealm` property of `NewPrinterCommandArgs` to the value provided for the form property `parent`
<5> binds the `realmName` property of `NewPrinterCommandArgs` to the value provided for the form property `realm`
<6> binds the `binaryParam` property of `BinaryCommandArgs` to the Base64-decoded value provided for the
form property `binaryField`

[TIP]
Please take into account that both defining and running a Macro task form are a much pleasant experience when
performed in the <<Admin Console>>.
====

[[tasks-scheduled]]
==== Scheduled

Scheduled tasks allow for the injection of custom logic into the <<core>> in the area of execution and scheduling.

When defining a scheduled task, the following information must be provided:

* job delegate class: Java class extending
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java[AbstractSchedTaskJobDelegate^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/4_0_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java[AbstractSchedTaskJobDelegate^]
endif::[]
providing the custom logic to execute
* scheduling information:
** when to start
** https://docs.spring.io/spring-framework/reference/6.2/integration/scheduling.html#scheduling-cron-expression[cron expression^]

[TIP]
====
Scheduled tasks are ideal for implementing periodic checks or clean-up operations, possibly in coordination with other
components; some examples:

* move users from "pending delete" to "deleted" status 15 days after they reached the "pending delete" status (requires
interaction with <<flowable-user-workflow-adapter>>)
* send out notification e-mails to users whose password is about to expire on an Identity Store
* disable all users not logging into the system for the past 6 months
====
