View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.provisioning.java.data;
20  
21  import java.util.Map;
22  import java.util.Objects;
23  import java.util.Optional;
24  import java.util.concurrent.ConcurrentHashMap;
25  import java.util.stream.Collectors;
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.syncope.common.lib.SyncopeClientException;
28  import org.apache.syncope.common.lib.command.CommandArgs;
29  import org.apache.syncope.common.lib.command.CommandTO;
30  import org.apache.syncope.common.lib.form.FormProperty;
31  import org.apache.syncope.common.lib.form.FormPropertyValue;
32  import org.apache.syncope.common.lib.form.SyncopeForm;
33  import org.apache.syncope.common.lib.to.ExecTO;
34  import org.apache.syncope.common.lib.to.FormPropertyDefTO;
35  import org.apache.syncope.common.lib.to.MacroTaskTO;
36  import org.apache.syncope.common.lib.to.NotificationTaskTO;
37  import org.apache.syncope.common.lib.to.PropagationTaskTO;
38  import org.apache.syncope.common.lib.to.ProvisioningTaskTO;
39  import org.apache.syncope.common.lib.to.PullTaskTO;
40  import org.apache.syncope.common.lib.to.PushTaskTO;
41  import org.apache.syncope.common.lib.to.SchedTaskTO;
42  import org.apache.syncope.common.lib.to.TaskTO;
43  import org.apache.syncope.common.lib.types.ClientExceptionType;
44  import org.apache.syncope.common.lib.types.IdRepoImplementationType;
45  import org.apache.syncope.common.lib.types.ImplementationEngine;
46  import org.apache.syncope.common.lib.types.JobType;
47  import org.apache.syncope.common.lib.types.MatchingRule;
48  import org.apache.syncope.common.lib.types.TaskType;
49  import org.apache.syncope.common.lib.types.UnmatchingRule;
50  import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
51  import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
52  import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
53  import org.apache.syncope.core.persistence.api.dao.NotFoundException;
54  import org.apache.syncope.core.persistence.api.dao.RealmDAO;
55  import org.apache.syncope.core.persistence.api.dao.TaskExecDAO;
56  import org.apache.syncope.core.persistence.api.entity.AnyType;
57  import org.apache.syncope.core.persistence.api.entity.EntityFactory;
58  import org.apache.syncope.core.persistence.api.entity.Implementation;
59  import org.apache.syncope.core.persistence.api.entity.task.AnyTemplatePullTask;
60  import org.apache.syncope.core.persistence.api.entity.task.FormPropertyDef;
61  import org.apache.syncope.core.persistence.api.entity.task.MacroTask;
62  import org.apache.syncope.core.persistence.api.entity.task.MacroTaskCommand;
63  import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
64  import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
65  import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask;
66  import org.apache.syncope.core.persistence.api.entity.task.PullTask;
67  import org.apache.syncope.core.persistence.api.entity.task.PushTask;
68  import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
69  import org.apache.syncope.core.persistence.api.entity.task.Task;
70  import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
71  import org.apache.syncope.core.persistence.api.entity.task.TaskUtils;
72  import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
73  import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
74  import org.apache.syncope.core.provisioning.api.job.JobNamer;
75  import org.apache.syncope.core.provisioning.api.macro.MacroActions;
76  import org.apache.syncope.core.provisioning.java.job.MacroJobDelegate;
77  import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
78  import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
79  import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
80  import org.apache.syncope.core.spring.implementation.ImplementationManager;
81  import org.quartz.Scheduler;
82  import org.quartz.SchedulerException;
83  import org.quartz.Trigger;
84  import org.quartz.TriggerKey;
85  import org.slf4j.Logger;
86  import org.slf4j.LoggerFactory;
87  import org.springframework.scheduling.quartz.SchedulerFactoryBean;
88  
89  public class TaskDataBinderImpl extends AbstractExecutableDatabinder implements TaskDataBinder {
90  
91      protected static final Logger LOG = LoggerFactory.getLogger(TaskDataBinder.class);
92  
93      protected final RealmDAO realmDAO;
94  
95      protected final ExternalResourceDAO resourceDAO;
96  
97      protected final TaskExecDAO taskExecDAO;
98  
99      protected final AnyTypeDAO anyTypeDAO;
100 
101     protected final ImplementationDAO implementationDAO;
102 
103     protected final EntityFactory entityFactory;
104 
105     protected final SchedulerFactoryBean scheduler;
106 
107     protected final TaskUtilsFactory taskUtilsFactory;
108 
109     protected final Map<String, MacroActions> perContextMacroActions = new ConcurrentHashMap<>();
110 
111     public TaskDataBinderImpl(
112             final RealmDAO realmDAO,
113             final ExternalResourceDAO resourceDAO,
114             final TaskExecDAO taskExecDAO,
115             final AnyTypeDAO anyTypeDAO,
116             final ImplementationDAO implementationDAO,
117             final EntityFactory entityFactory,
118             final SchedulerFactoryBean scheduler,
119             final TaskUtilsFactory taskUtilsFactory) {
120 
121         this.realmDAO = realmDAO;
122         this.resourceDAO = resourceDAO;
123         this.taskExecDAO = taskExecDAO;
124         this.anyTypeDAO = anyTypeDAO;
125         this.implementationDAO = implementationDAO;
126         this.entityFactory = entityFactory;
127         this.scheduler = scheduler;
128         this.taskUtilsFactory = taskUtilsFactory;
129     }
130 
131     protected void fill(final ProvisioningTask<?> provisioningTask, final ProvisioningTaskTO provisioningTaskTO) {
132         if (provisioningTask instanceof PushTask && provisioningTaskTO instanceof PushTaskTO) {
133             PushTask pushTask = (PushTask) provisioningTask;
134             PushTaskTO pushTaskTO = (PushTaskTO) provisioningTaskTO;
135 
136             Implementation jobDelegate = pushTaskTO.getJobDelegate() == null
137                     ? implementationDAO.findByType(IdRepoImplementationType.TASKJOB_DELEGATE).stream().
138                             filter(impl -> PushJobDelegate.class.getSimpleName().equals(impl.getKey())).
139                             findFirst().orElse(null)
140                     : implementationDAO.find(pushTaskTO.getJobDelegate());
141             if (jobDelegate == null) {
142                 jobDelegate = entityFactory.newEntity(Implementation.class);
143                 jobDelegate.setKey(PushJobDelegate.class.getSimpleName());
144                 jobDelegate.setEngine(ImplementationEngine.JAVA);
145                 jobDelegate.setType(IdRepoImplementationType.TASKJOB_DELEGATE);
146                 jobDelegate.setBody(PushJobDelegate.class.getName());
147                 jobDelegate = implementationDAO.save(jobDelegate);
148             }
149             pushTask.setJobDelegate(jobDelegate);
150 
151             pushTask.setSourceRealm(realmDAO.findByFullPath(pushTaskTO.getSourceRealm()));
152 
153             pushTask.setMatchingRule(pushTaskTO.getMatchingRule() == null
154                     ? MatchingRule.LINK : pushTaskTO.getMatchingRule());
155             pushTask.setUnmatchingRule(pushTaskTO.getUnmatchingRule() == null
156                     ? UnmatchingRule.ASSIGN : pushTaskTO.getUnmatchingRule());
157 
158             pushTaskTO.getFilters().forEach((type, fiql) -> {
159                 AnyType anyType = anyTypeDAO.find(type);
160                 if (anyType == null) {
161                     LOG.debug("Invalid AnyType {} specified, ignoring...", type);
162                 } else {
163                     pushTask.getFilters().put(anyType.getKey(), fiql);
164                 }
165             });
166             // remove all filters not contained in the TO
167             pushTask.getFilters().entrySet().
168                     removeIf(filter -> !pushTaskTO.getFilters().containsKey(filter.getKey()));
169         } else if (provisioningTask instanceof PullTask && provisioningTaskTO instanceof PullTaskTO) {
170             PullTask pullTask = (PullTask) provisioningTask;
171             PullTaskTO pullTaskTO = (PullTaskTO) provisioningTaskTO;
172 
173             Implementation jobDelegate = pullTaskTO.getJobDelegate() == null
174                     ? implementationDAO.findByType(IdRepoImplementationType.TASKJOB_DELEGATE).stream().
175                             filter(impl -> PullJobDelegate.class.getSimpleName().equals(impl.getKey())).
176                             findFirst().orElse(null)
177                     : implementationDAO.find(pullTaskTO.getJobDelegate());
178             if (jobDelegate == null) {
179                 jobDelegate = entityFactory.newEntity(Implementation.class);
180                 jobDelegate.setKey(PullJobDelegate.class.getSimpleName());
181                 jobDelegate.setEngine(ImplementationEngine.JAVA);
182                 jobDelegate.setType(IdRepoImplementationType.TASKJOB_DELEGATE);
183                 jobDelegate.setBody(PullJobDelegate.class.getName());
184                 jobDelegate = implementationDAO.save(jobDelegate);
185             }
186             pullTask.setJobDelegate(jobDelegate);
187 
188             pullTask.setPullMode(pullTaskTO.getPullMode());
189 
190             if (pullTaskTO.getReconFilterBuilder() == null) {
191                 pullTask.setReconFilterBuilder(null);
192             } else {
193                 Optional.ofNullable(implementationDAO.find(pullTaskTO.getReconFilterBuilder())).ifPresentOrElse(
194                         pullTask::setReconFilterBuilder,
195                         () -> LOG.debug("Invalid Implementation {}, ignoring...", pullTaskTO.getReconFilterBuilder()));
196             }
197 
198             pullTask.setDestinationRealm(realmDAO.findByFullPath(pullTaskTO.getDestinationRealm()));
199 
200             pullTask.setMatchingRule(pullTaskTO.getMatchingRule() == null
201                     ? MatchingRule.UPDATE : pullTaskTO.getMatchingRule());
202             pullTask.setUnmatchingRule(pullTaskTO.getUnmatchingRule() == null
203                     ? UnmatchingRule.PROVISION : pullTaskTO.getUnmatchingRule());
204 
205             // validate JEXL expressions from templates and proceed if fine
206             TemplateUtils.check(pullTaskTO.getTemplates(), ClientExceptionType.InvalidPullTask);
207             pullTaskTO.getTemplates().forEach((type, template) -> {
208                 AnyType anyType = anyTypeDAO.find(type);
209                 if (anyType == null) {
210                     LOG.debug("Invalid AnyType {} specified, ignoring...", type);
211                 } else {
212                     AnyTemplatePullTask anyTemplate = pullTask.getTemplate(anyType.getKey()).orElse(null);
213                     if (anyTemplate == null) {
214                         anyTemplate = entityFactory.newEntity(AnyTemplatePullTask.class);
215                         anyTemplate.setAnyType(anyType);
216                         anyTemplate.setPullTask(pullTask);
217 
218                         pullTask.add(anyTemplate);
219                     }
220                     anyTemplate.set(template);
221                 }
222             });
223             // remove all templates not contained in the TO
224             pullTask.getTemplates().
225                     removeIf(anyTemplate -> !pullTaskTO.getTemplates().containsKey(anyTemplate.getAnyType().getKey()));
226 
227             pullTask.setRemediation(pullTaskTO.isRemediation());
228         }
229 
230         // 3. fill the remaining fields
231         provisioningTask.setPerformCreate(provisioningTaskTO.isPerformCreate());
232         provisioningTask.setPerformUpdate(provisioningTaskTO.isPerformUpdate());
233         provisioningTask.setPerformDelete(provisioningTaskTO.isPerformDelete());
234         provisioningTask.setSyncStatus(provisioningTaskTO.isSyncStatus());
235 
236         provisioningTaskTO.getActions().forEach(
237                 action -> Optional.ofNullable(implementationDAO.find(action)).ifPresentOrElse(
238                         provisioningTask::add,
239                         () -> LOG.debug("Invalid Implementation {}, ignoring...", action)));
240         // remove all implementations not contained in the TO
241         provisioningTask.getActions().removeIf(impl -> !provisioningTaskTO.getActions().contains(impl.getKey()));
242 
243         provisioningTask.setConcurrentSettings(provisioningTaskTO.getConcurrentSettings());
244     }
245 
246     protected void fill(final MacroTask macroTask, final MacroTaskTO macroTaskTO) {
247         macroTask.setRealm(Optional.ofNullable(realmDAO.findByFullPath(macroTaskTO.getRealm())).
248                 orElseThrow(() -> new NotFoundException("Realm " + macroTaskTO.getRealm())));
249 
250         macroTask.getCommands().clear();
251         macroTaskTO.getCommands().
252                 forEach(command -> Optional.ofNullable(implementationDAO.find(command.getKey())).ifPresentOrElse(
253                 impl -> {
254                     try {
255                         CommandArgs args = command.getArgs();
256                         if (args == null) {
257                             args = ImplementationManager.emptyArgs(impl);
258                         }
259 
260                         MacroTaskCommand macroTaskCommand = entityFactory.newEntity(MacroTaskCommand.class);
261                         macroTaskCommand.setCommand(impl);
262                         macroTaskCommand.setArgs(args);
263 
264                         macroTaskCommand.setMacroTask(macroTask);
265                         macroTask.add(macroTaskCommand);
266                     } catch (Exception e) {
267                         LOG.error("While adding Command {} to Macro", impl.getKey(), e);
268 
269                         SyncopeClientException sce = SyncopeClientException.build(
270                                 ClientExceptionType.InvalidImplementationType);
271                         sce.getElements().add("While adding Command " + impl.getKey() + ": " + e.getMessage());
272                         throw sce;
273                     }
274                 },
275                 () -> LOG.error("Could not find Command {}", command.getKey())));
276 
277         macroTask.setContinueOnError(macroTaskTO.isContinueOnError());
278         macroTask.setSaveExecs(macroTaskTO.isSaveExecs());
279 
280         macroTask.getFormPropertyDefs().clear();
281         macroTaskTO.getFormPropertyDefs().forEach(fpdTO -> {
282             FormPropertyDef fpd = entityFactory.newEntity(FormPropertyDef.class);
283             fpd.setKey(fpdTO.getKey());
284             fpd.setName(fpdTO.getName());
285             fpd.setType(fpdTO.getType());
286             fpd.setReadable(fpdTO.isReadable());
287             fpd.setWritable(fpdTO.isWritable());
288             fpd.setStringRegExp(fpdTO.getStringRegEx());
289             fpd.setRequired(fpdTO.isRequired());
290             fpd.setDatePattern(fpdTO.getDatePattern());
291             fpd.setEnumValues(fpdTO.getEnumValues());
292             fpd.setDropdownSingleSelection(fpdTO.isDropdownSingleSelection());
293             fpd.setDropdownFreeForm(fpdTO.isDropdownFreeForm());
294 
295             fpd.setMacroTask(macroTask);
296             macroTask.add(fpd);
297         });
298 
299         if (macroTaskTO.getMacroActions() == null) {
300             macroTask.setMacroAction(null);
301         } else {
302             Optional.ofNullable(implementationDAO.find(macroTaskTO.getMacroActions())).ifPresentOrElse(
303                     macroTask::setMacroAction,
304                     () -> LOG.debug("Invalid Implementation {}, ignoring...", macroTaskTO.getMacroActions()));
305         }
306     }
307 
308     @Override
309     public SchedTask createSchedTask(final SchedTaskTO taskTO, final TaskUtils taskUtils) {
310         Class<? extends TaskTO> taskTOClass = taskUtils.taskTOClass();
311         if (taskTOClass == null || !taskTOClass.equals(taskTO.getClass())) {
312             throw new IllegalArgumentException(String.format("Expected %s, found %s", taskTOClass, taskTO.getClass()));
313         }
314 
315         SchedTask task = taskUtils.newTask();
316         task.setStartAt(taskTO.getStartAt());
317         task.setCronExpression(taskTO.getCronExpression());
318         task.setName(taskTO.getName());
319         task.setDescription(taskTO.getDescription());
320         task.setActive(taskTO.isActive());
321 
322         if (taskUtils.getType() == TaskType.SCHEDULED) {
323             task.setJobDelegate(Optional.ofNullable(implementationDAO.find(taskTO.getJobDelegate())).
324                     orElseThrow(() -> new NotFoundException("JobDelegate " + taskTO.getJobDelegate())));
325         } else if (taskTO instanceof MacroTaskTO) {
326             MacroTaskTO macroTaskTO = (MacroTaskTO) taskTO;
327             MacroTask macroTask = (MacroTask) task;
328 
329             Implementation jobDelegate = macroTaskTO.getJobDelegate() == null
330                     ? implementationDAO.findByType(IdRepoImplementationType.TASKJOB_DELEGATE).stream().
331                             filter(impl -> MacroJobDelegate.class.getName().equals(impl.getBody())).
332                             findFirst().orElse(null)
333                     : implementationDAO.find(macroTaskTO.getJobDelegate());
334             if (jobDelegate == null) {
335                 jobDelegate = entityFactory.newEntity(Implementation.class);
336                 jobDelegate.setKey(MacroJobDelegate.class.getSimpleName());
337                 jobDelegate.setEngine(ImplementationEngine.JAVA);
338                 jobDelegate.setType(IdRepoImplementationType.TASKJOB_DELEGATE);
339                 jobDelegate.setBody(MacroJobDelegate.class.getName());
340                 jobDelegate = implementationDAO.save(jobDelegate);
341             }
342             macroTask.setJobDelegate(jobDelegate);
343 
344             macroTask.setRealm(Optional.ofNullable(realmDAO.findByFullPath(macroTaskTO.getRealm())).
345                     orElseThrow(() -> new NotFoundException("Realm " + macroTaskTO.getRealm())));
346 
347             fill(macroTask, macroTaskTO);
348         } else if (taskTO instanceof ProvisioningTaskTO) {
349             ProvisioningTaskTO provisioningTaskTO = (ProvisioningTaskTO) taskTO;
350             ProvisioningTask<?> provisioningTask = (ProvisioningTask<?>) task;
351 
352             provisioningTask.setResource(Optional.ofNullable(resourceDAO.find(provisioningTaskTO.getResource())).
353                     orElseThrow(() -> new NotFoundException("Resource " + provisioningTaskTO.getResource())));
354 
355             fill(provisioningTask, provisioningTaskTO);
356         }
357 
358         return task;
359     }
360 
361     @Override
362     public void updateSchedTask(final SchedTask task, final SchedTaskTO taskTO, final TaskUtils taskUtils) {
363         Class<? extends TaskTO> taskTOClass = taskUtils.taskTOClass();
364         if (taskTOClass == null || !taskTOClass.equals(taskTO.getClass())) {
365             throw new IllegalArgumentException(String.format("Expected %s, found %s", taskTOClass, taskTO.getClass()));
366         }
367 
368         if (StringUtils.isBlank(taskTO.getName())) {
369             SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
370             sce.getElements().add("name");
371             throw sce;
372         }
373 
374         task.setName(taskTO.getName());
375         task.setDescription(taskTO.getDescription());
376         task.setCronExpression(taskTO.getCronExpression());
377         task.setActive(taskTO.isActive());
378 
379         if (task instanceof MacroTask) {
380             fill((MacroTask) task, (MacroTaskTO) taskTO);
381         } else if (task instanceof ProvisioningTask) {
382             fill((ProvisioningTask) task, (ProvisioningTaskTO) taskTO);
383         }
384     }
385 
386     @Override
387     public String buildRefDesc(final Task<?> task) {
388         return taskUtilsFactory.getInstance(task).getType().name() + ' '
389                 + "Task "
390                 + task.getKey() + ' '
391                 + (task instanceof SchedTask
392                         ? SchedTask.class.cast(task).getName()
393                         : task instanceof PropagationTask
394                                 ? PropagationTask.class.cast(task).getConnObjectKey()
395                                 : StringUtils.EMPTY);
396     }
397 
398     @Override
399     public ExecTO getExecTO(final TaskExec<?> execution) {
400         ExecTO execTO = new ExecTO();
401         execTO.setKey(execution.getKey());
402         execTO.setStatus(execution.getStatus());
403         execTO.setMessage(execution.getMessage());
404         execTO.setStart(execution.getStart());
405         execTO.setEnd(execution.getEnd());
406         execTO.setExecutor(execution.getExecutor());
407 
408         if (execution.getTask() != null && execution.getTask().getKey() != null) {
409             execTO.setJobType(JobType.TASK);
410             execTO.setRefKey(execution.getTask().getKey());
411             execTO.setRefDesc(buildRefDesc(execution.getTask()));
412         }
413 
414         return execTO;
415     }
416 
417     protected void fill(final SchedTaskTO schedTaskTO, final SchedTask schedTask) {
418         schedTaskTO.setName(schedTask.getName());
419         schedTaskTO.setDescription(schedTask.getDescription());
420         schedTaskTO.setCronExpression(schedTask.getCronExpression());
421         schedTaskTO.setActive(schedTask.isActive());
422 
423         schedTaskTO.setLastExec(schedTaskTO.getStart());
424 
425         String triggerName = JobNamer.getTriggerName(JobNamer.getJobKey(schedTask).getName());
426         try {
427             Trigger trigger = scheduler.getScheduler().getTrigger(new TriggerKey(triggerName, Scheduler.DEFAULT_GROUP));
428             if (trigger != null) {
429                 schedTaskTO.setLastExec(toOffsetDateTime(trigger.getPreviousFireTime()));
430                 schedTaskTO.setNextExec(toOffsetDateTime(trigger.getNextFireTime()));
431             }
432         } catch (SchedulerException e) {
433             LOG.warn("While trying to get to " + triggerName, e);
434         }
435 
436         if (schedTaskTO instanceof ProvisioningTaskTO && schedTask instanceof ProvisioningTask) {
437             ProvisioningTaskTO provisioningTaskTO = (ProvisioningTaskTO) schedTaskTO;
438             ProvisioningTask<?> provisioningTask = (ProvisioningTask<?>) schedTask;
439 
440             provisioningTaskTO.setResource(provisioningTask.getResource().getKey());
441 
442             provisioningTaskTO.getActions().addAll(
443                     provisioningTask.getActions().stream().map(Implementation::getKey).collect(Collectors.toList()));
444 
445             provisioningTaskTO.setPerformCreate(provisioningTask.isPerformCreate());
446             provisioningTaskTO.setPerformUpdate(provisioningTask.isPerformUpdate());
447             provisioningTaskTO.setPerformDelete(provisioningTask.isPerformDelete());
448             provisioningTaskTO.setSyncStatus(provisioningTask.isSyncStatus());
449 
450             provisioningTaskTO.setConcurrentSettings(provisioningTask.getConcurrentSettings());
451         }
452     }
453 
454     @Override
455     public <T extends TaskTO> T getTaskTO(final Task<?> task, final TaskUtils taskUtils, final boolean details) {
456         T taskTO = taskUtils.newTaskTO();
457         taskTO.setKey(task.getKey());
458 
459         Optional.ofNullable(taskExecDAO.findLatestStarted(taskUtils.getType(), task)).ifPresentOrElse(
460                 latestExec -> {
461                     taskTO.setLatestExecStatus(latestExec.getStatus());
462                     taskTO.setStart(latestExec.getStart());
463                     taskTO.setEnd(latestExec.getEnd());
464                     taskTO.setLastExecutor(latestExec.getExecutor());
465                 },
466                 () -> taskTO.setLatestExecStatus(StringUtils.EMPTY));
467 
468         if (details) {
469             task.getExecs().stream().
470                     filter(Objects::nonNull).
471                     forEach(execution -> taskTO.getExecutions().add(getExecTO(execution)));
472         }
473 
474         switch (taskUtils.getType()) {
475             case PROPAGATION:
476                 PropagationTask propagationTask = (PropagationTask) task;
477                 PropagationTaskTO propagationTaskTO = (PropagationTaskTO) taskTO;
478 
479                 propagationTaskTO.setOperation(propagationTask.getOperation());
480                 propagationTaskTO.setConnObjectKey(propagationTask.getConnObjectKey());
481                 propagationTaskTO.setOldConnObjectKey(propagationTask.getOldConnObjectKey());
482                 propagationTaskTO.setPropagationData(propagationTask.getSerializedPropagationData());
483                 propagationTaskTO.setResource(propagationTask.getResource().getKey());
484                 propagationTaskTO.setObjectClassName(propagationTask.getObjectClassName());
485                 propagationTaskTO.setAnyTypeKind(propagationTask.getAnyTypeKind());
486                 propagationTaskTO.setAnyType(propagationTask.getAnyType());
487                 propagationTaskTO.setEntityKey(propagationTask.getEntityKey());
488                 break;
489 
490             case SCHEDULED:
491                 SchedTask schedTask = (SchedTask) task;
492                 SchedTaskTO schedTaskTO = (SchedTaskTO) taskTO;
493 
494                 fill(schedTaskTO, schedTask);
495 
496                 schedTaskTO.setJobDelegate(schedTask.getJobDelegate().getKey());
497                 break;
498 
499             case MACRO:
500                 MacroTask macroTask = (MacroTask) task;
501                 MacroTaskTO macroTaskTO = (MacroTaskTO) taskTO;
502 
503                 fill(macroTaskTO, macroTask);
504 
505                 macroTaskTO.setJobDelegate(macroTask.getJobDelegate().getKey());
506                 macroTaskTO.setRealm(macroTask.getRealm().getFullPath());
507 
508                 macroTask.getCommands().forEach(mct -> macroTaskTO.getCommands().add(
509                         new CommandTO.Builder(mct.getCommand().getKey()).args(mct.getArgs()).build()));
510 
511                 macroTaskTO.setContinueOnError(macroTask.isContinueOnError());
512                 macroTaskTO.setSaveExecs(macroTask.isSaveExecs());
513 
514                 macroTask.getFormPropertyDefs().forEach(fpd -> {
515                     FormPropertyDefTO fpdTO = new FormPropertyDefTO();
516                     fpdTO.setKey(fpd.getKey());
517                     fpdTO.setName(fpd.getName());
518                     fpdTO.setType(fpd.getType());
519                     fpdTO.setReadable(fpd.isReadable());
520                     fpdTO.setWritable(fpd.isWritable());
521                     fpdTO.setRequired(fpd.isRequired());
522                     fpdTO.setStringRegEx(fpd.getStringRegEx());
523                     fpdTO.setDatePattern(fpd.getDatePattern());
524                     fpdTO.getEnumValues().putAll(fpd.getEnumValues());
525                     fpdTO.setDropdownSingleSelection(fpd.isDropdownSingleSelection());
526                     fpdTO.setDropdownFreeForm(fpd.isDropdownFreeForm());
527 
528                     macroTaskTO.getFormPropertyDefs().add(fpdTO);
529                 });
530 
531                 Optional.ofNullable(macroTask.getMacroActions()).
532                         ifPresent(fv -> macroTaskTO.setMacroActions(fv.getKey()));
533                 break;
534 
535             case PULL:
536                 PullTask pullTask = (PullTask) task;
537                 PullTaskTO pullTaskTO = (PullTaskTO) taskTO;
538 
539                 fill(pullTaskTO, pullTask);
540 
541                 pullTaskTO.setDestinationRealm(pullTask.getDestinationRealm().getFullPath());
542                 pullTaskTO.setMatchingRule(pullTask.getMatchingRule() == null
543                         ? MatchingRule.UPDATE : pullTask.getMatchingRule());
544                 pullTaskTO.setUnmatchingRule(pullTask.getUnmatchingRule() == null
545                         ? UnmatchingRule.PROVISION : pullTask.getUnmatchingRule());
546                 pullTaskTO.setPullMode(pullTask.getPullMode());
547 
548                 Optional.ofNullable(pullTask.getReconFilterBuilder()).
549                         ifPresent(rfb -> pullTaskTO.setReconFilterBuilder(rfb.getKey()));
550 
551                 pullTask.getTemplates().
552                         forEach(template -> pullTaskTO.getTemplates().
553                         put(template.getAnyType().getKey(), template.get()));
554 
555                 pullTaskTO.setRemediation(pullTask.isRemediation());
556                 break;
557 
558             case PUSH:
559                 PushTask pushTask = (PushTask) task;
560                 PushTaskTO pushTaskTO = (PushTaskTO) taskTO;
561 
562                 fill(pushTaskTO, pushTask);
563 
564                 pushTaskTO.setSourceRealm(pushTask.getSourceRealm().getFullPath());
565                 pushTaskTO.setMatchingRule(pushTask.getMatchingRule() == null
566                         ? MatchingRule.LINK : pushTask.getMatchingRule());
567                 pushTaskTO.setUnmatchingRule(pushTask.getUnmatchingRule() == null
568                         ? UnmatchingRule.ASSIGN : pushTask.getUnmatchingRule());
569 
570                 pushTaskTO.getFilters().putAll(pushTask.getFilters());
571                 break;
572 
573             case NOTIFICATION:
574                 NotificationTask notificationTask = (NotificationTask) task;
575                 NotificationTaskTO notificationTaskTO = (NotificationTaskTO) taskTO;
576 
577                 notificationTaskTO.setNotification(notificationTask.getNotification().getKey());
578                 notificationTaskTO.setAnyTypeKind(notificationTask.getAnyTypeKind());
579                 notificationTaskTO.setEntityKey(notificationTask.getEntityKey());
580                 notificationTaskTO.setSender(notificationTask.getSender());
581                 notificationTaskTO.getRecipients().addAll(notificationTask.getRecipients());
582                 notificationTaskTO.setSubject(notificationTask.getSubject());
583                 notificationTaskTO.setHtmlBody(notificationTask.getHtmlBody());
584                 notificationTaskTO.setTextBody(notificationTask.getTextBody());
585                 notificationTaskTO.setExecuted(notificationTask.isExecuted());
586                 if (notificationTask.isExecuted() && StringUtils.isBlank(taskTO.getLatestExecStatus())) {
587                     taskTO.setLatestExecStatus("[EXECUTED]");
588                 }
589                 notificationTaskTO.setTraceLevel(notificationTask.getTraceLevel());
590                 break;
591 
592             default:
593         }
594 
595         return taskTO;
596     }
597 
598     @Override
599     public SyncopeForm getMacroTaskForm(final MacroTask task) {
600         if (task.getFormPropertyDefs().isEmpty()) {
601             throw new NotFoundException("No form properties defined for MacroTask " + task.getKey());
602         }
603 
604         Optional<MacroActions> actions;
605         if (task.getMacroActions() == null) {
606             actions = Optional.empty();
607         } else {
608             try {
609                 actions = Optional.of(ImplementationManager.build(
610                         task.getMacroActions(),
611                         () -> perContextMacroActions.get(task.getMacroActions().getKey()),
612                         instance -> perContextMacroActions.put(task.getMacroActions().getKey(), instance)));
613             } catch (Exception e) {
614                 LOG.error("Could not build {}", task.getMacroActions().getKey(), e);
615 
616                 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidImplementation);
617                 sce.getElements().add("Could not build " + task.getMacroActions().getKey());
618                 throw sce;
619             }
620         }
621 
622         SyncopeForm form = new SyncopeForm();
623 
624         form.getProperties().addAll(task.getFormPropertyDefs().stream().map(fpd -> {
625             FormProperty prop = new FormProperty();
626             prop.setId(fpd.getKey());
627             prop.setName(fpd.getName());
628             prop.setReadable(fpd.isReadable());
629             prop.setRequired(fpd.isRequired());
630             prop.setWritable(fpd.isWritable());
631             prop.setType(fpd.getType());
632             actions.flatMap(a -> a.getDefaultValue(fpd.getKey())).ifPresent(v -> prop.setValue(v));
633             switch (prop.getType()) {
634                 case String:
635                     prop.setStringRegEx(fpd.getStringRegEx());
636                     break;
637 
638                 case Date:
639                     prop.setDatePattern(fpd.getDatePattern());
640                     break;
641 
642                 case Enum:
643                     fpd.getEnumValues().
644                             forEach((key, value) -> prop.getEnumValues().add(new FormPropertyValue(key, value)));
645                     break;
646 
647                 case Dropdown:
648                     actions.ifPresent(a -> a.getDropdownValues(fpd.getKey()).
649                             forEach((key, value) -> prop.getDropdownValues().add(new FormPropertyValue(key, value))));
650                     prop.setDropdownSingleSelection(fpd.isDropdownSingleSelection());
651                     prop.setDropdownFreeForm(fpd.isDropdownFreeForm());
652                     break;
653 
654                 default:
655             }
656             return prop;
657         }).collect(Collectors.toList()));
658 
659         return form;
660     }
661 }