This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 3_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/3_0_X by this push:
     new ea65d9f168 [SYNCOPE-1840] Reworking MacroTask's form properties storage
ea65d9f168 is described below

commit ea65d9f168703d74c5f9b7abba1031ce201405d9
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Thu Nov 7 16:12:22 2024 +0100

    [SYNCOPE-1840] Reworking MacroTask's form properties storage
---
 .../console/panels/SchemaTypeWizardBuilder.java    |  3 +-
 .../client/console/rest/TaskRestClient.java        |  4 +-
 .../console/tasks/FormPropertyDefsPanel.java       | 14 ++---
 .../console/tasks/FormPropertyDefsPanel.html       |  6 +--
 .../console/tasks/FormPropertyDefsPanel.properties |  1 +
 .../tasks/FormPropertyDefsPanel_fr_CA.properties   |  1 +
 .../tasks/FormPropertyDefsPanel_it.properties      |  1 +
 .../tasks/FormPropertyDefsPanel_ja.properties      |  1 +
 .../tasks/FormPropertyDefsPanel_pt_BR.properties   |  1 +
 .../tasks/FormPropertyDefsPanel_ru.properties      |  1 +
 .../syncope/common/lib/to/FormPropertyDefTO.java   | 16 ++++++
 .../common/rest/api/service/TaskService.java       |  3 +-
 .../org/apache/syncope/core/logic/TaskLogic.java   |  5 +-
 .../core/rest/cxf/service/TaskServiceImpl.java     | 19 ++++++-
 .../api/entity/task/FormPropertyDef.java           | 10 +++-
 .../core/persistence/jpa/dao/JPATaskDAO.java       |  6 ++-
 .../jpa/entity/task/JPAFormPropertyDef.java        | 63 ++++++++++++++++++++--
 .../core/persistence/jpa/inner/TaskTest.java       |  6 ++-
 .../core/provisioning/api/data/TaskDataBinder.java |  3 +-
 .../provisioning/java/data/TaskDataBinderImpl.java | 19 +++----
 .../provisioning/java/job/MacroJobDelegate.java    | 34 ++++++------
 .../apache/syncope/fit/core/MacroTaskITCase.java   | 11 ++--
 22 files changed, 169 insertions(+), 59 deletions(-)

diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java
index ab6e018b0f..9570a917d6 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java
@@ -179,8 +179,7 @@ public class SchemaTypeWizardBuilder extends 
BaseAjaxWizardBuilder<SchemaTO> {
                             LOG.error("Invalid Locale: {}", 
validatable.getValue(), e);
                             validatable.error(new ValidationError("Invalid 
Locale: " + validatable.getValue()));
 
-                            RequestCycle.get().find(AjaxRequestTarget.class).
-                                    ifPresent(target -> 
target.add(Labels.this));
+                            
RequestCycle.get().find(AjaxRequestTarget.class).ifPresent(t -> 
t.add(Labels.this));
                         }
                     });
                     item.add(locale);
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
index ad1e1bcbae..1023a794a1 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
@@ -25,6 +25,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.lib.batch.BatchRequest;
 import org.apache.syncope.client.ui.commons.DateOps;
 import org.apache.syncope.common.lib.form.SyncopeForm;
@@ -194,7 +195,8 @@ public class TaskRestClient extends BaseRestClient 
implements ExecutionRestClien
     }
 
     public SyncopeForm getMacroTaskForm(final String taskKey) {
-        return getService(TaskService.class).getMacroTaskForm(taskKey);
+        return getService(TaskService.class).
+                getMacroTaskForm(taskKey, 
SyncopeConsoleSession.get().getLocale().toLanguageTag());
     }
 
     public void delete(final TaskType type, final String taskKey) {
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.java
index ccb458da64..9859f12306 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.java
@@ -21,6 +21,7 @@ package org.apache.syncope.client.console.tasks;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Optional;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
@@ -89,13 +90,6 @@ public class FormPropertyDefsPanel extends 
AbstractModalPanel<MacroTaskTO> {
             protected void populateItem(final ListItem<FormPropertyDefTO> 
item) {
                 FormPropertyDefTO fpd = item.getModelObject();
 
-                AjaxTextFieldPanel key = new AjaxTextFieldPanel(
-                        "key",
-                        "key",
-                        new PropertyModel<>(fpd, "key"),
-                        true);
-                item.add(key.setRequired(true).hideLabel());
-
                 AjaxTextFieldPanel name = new AjaxTextFieldPanel(
                         "name",
                         "name",
@@ -103,6 +97,12 @@ public class FormPropertyDefsPanel extends 
AbstractModalPanel<MacroTaskTO> {
                         true);
                 item.add(name.setRequired(true).hideLabel());
 
+                AjaxGridFieldPanel<String, Locale, String> labels = new 
AjaxGridFieldPanel<>(
+                        "labels",
+                        "labels",
+                        new PropertyModel<>(fpd, "labels"));
+                item.add(labels.hideLabel());
+
                 AjaxCheckBoxPanel readable = new AjaxCheckBoxPanel(
                         "readable",
                         "readable",
diff --git 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.html
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.html
index 933b16e786..7ab57b2801 100644
--- 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.html
+++ 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.html
@@ -25,8 +25,8 @@ under the License.
              wicket:id="propertyDefContainer">
         <tbody>
           <tr>
-            <th><wicket:message key="key"/></th>
             <th><wicket:message key="name"/></th>
+            <th><wicket:message key="labels"/></th>
             <th><wicket:message key="readable"/></th>
             <th><wicket:message key="writable"/></th>
             <th><wicket:message key="required"/></th>
@@ -37,10 +37,10 @@ under the License.
 
           <tr wicket:id="propertyDefs">
             <td>
-              <span wicket:id="key"/>
+              <span wicket:id="name"/>
             </td>
             <td>
-              <span wicket:id="name"/>
+              <span wicket:id="labels"/>
             </td>
             <td>
               <span wicket:id="readable"/>
diff --git 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.properties
index 5831fc80ca..c61b80b25d 100644
--- 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.properties
+++ 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel.properties
@@ -24,3 +24,4 @@ enumValues=Values
 datePattern=Pattern
 dropdownSingleSelection=Single Selection
 dropdownFreeForm=Free Form
+labels=Labels
diff --git 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_fr_CA.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_fr_CA.properties
index 5831fc80ca..c61b80b25d 100644
--- 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_fr_CA.properties
+++ 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_fr_CA.properties
@@ -24,3 +24,4 @@ enumValues=Values
 datePattern=Pattern
 dropdownSingleSelection=Single Selection
 dropdownFreeForm=Free Form
+labels=Labels
diff --git 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_it.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_it.properties
index 5cd41d2b8b..aa7ba2735a 100644
--- 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_it.properties
+++ 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_it.properties
@@ -24,3 +24,4 @@ enumValues=Valori
 datePattern=Modello
 dropdownSingleSelection=Selezione Singola
 dropdownFreeForm=Modalit\u00e0 libera
+labels=Etichette
diff --git 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_ja.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_ja.properties
index 5831fc80ca..c61b80b25d 100644
--- 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_ja.properties
+++ 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_ja.properties
@@ -24,3 +24,4 @@ enumValues=Values
 datePattern=Pattern
 dropdownSingleSelection=Single Selection
 dropdownFreeForm=Free Form
+labels=Labels
diff --git 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_pt_BR.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_pt_BR.properties
index 5831fc80ca..c61b80b25d 100644
--- 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_pt_BR.properties
+++ 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_pt_BR.properties
@@ -24,3 +24,4 @@ enumValues=Values
 datePattern=Pattern
 dropdownSingleSelection=Single Selection
 dropdownFreeForm=Free Form
+labels=Labels
diff --git 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_ru.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_ru.properties
index 5831fc80ca..c61b80b25d 100644
--- 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_ru.properties
+++ 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/FormPropertyDefsPanel_ru.properties
@@ -24,3 +24,4 @@ enumValues=Values
 datePattern=Pattern
 dropdownSingleSelection=Single Selection
 dropdownFreeForm=Free Form
+labels=Labels
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/FormPropertyDefTO.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/FormPropertyDefTO.java
index 4f1d24aba5..a7c5df45aa 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/FormPropertyDefTO.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/FormPropertyDefTO.java
@@ -18,7 +18,10 @@
  */
 package org.apache.syncope.common.lib.to;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.regex.Pattern;
 import org.apache.commons.lang3.builder.EqualsBuilder;
@@ -33,6 +36,8 @@ public class FormPropertyDefTO implements NamedEntityTO {
 
     private String name;
 
+    private final Map<Locale, String> labels = new HashMap<>();
+
     private FormPropertyType type;
 
     private boolean readable = true;
@@ -71,6 +76,15 @@ public class FormPropertyDefTO implements NamedEntityTO {
         this.name = name;
     }
 
+    @JsonIgnore
+    public String getLabel(final Locale locale) {
+        return labels.getOrDefault(locale, key);
+    }
+
+    public Map<Locale, String> getLabels() {
+        return labels;
+    }
+
     public FormPropertyType getType() {
         return type;
     }
@@ -144,6 +158,7 @@ public class FormPropertyDefTO implements NamedEntityTO {
         return new HashCodeBuilder().
                 append(key).
                 append(name).
+                append(labels).
                 append(type).
                 append(readable).
                 append(writable).
@@ -171,6 +186,7 @@ public class FormPropertyDefTO implements NamedEntityTO {
         return new EqualsBuilder().
                 append(key, other.key).
                 append(name, other.name).
+                append(labels, other.labels).
                 append(type, other.type).
                 append(readable, other.readable).
                 append(writable, other.writable).
diff --git 
a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
 
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
index d880e9b821..a01ed2b6da 100644
--- 
a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
+++ 
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
@@ -169,12 +169,13 @@ public interface TaskService extends ExecutableService {
      * Fetches the form to fill and submit for execution, for the given macro 
task (if defined).
      *
      * @param key macro task key
+     * @param locale form locale
      * @return the form to fill and submit for execution, for the given macro 
task (if defined)
      */
     @GET
     @Path("MACRO/{key}/form")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    SyncopeForm getMacroTaskForm(@NotNull @PathParam("key") String key);
+    SyncopeForm getMacroTaskForm(@NotNull @PathParam("key") String key, 
@NotNull @QueryParam("locale") String locale);
 
     /**
      * Executes the macro task matching the given specs, with the provided 
form as input.
diff --git 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
index 1b2edcbf22..4b28b4574d 100644
--- 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
+++ 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Method;
 import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -284,14 +285,14 @@ public class TaskLogic extends 
AbstractExecutableLogic<TaskTO> {
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.TASK_READ + "')")
     @Transactional(readOnly = true)
-    public SyncopeForm getMacroTaskForm(final String key) {
+    public SyncopeForm getMacroTaskForm(final String key, final Locale locale) 
{
         MacroTask task = taskDAO.find(key).
                 filter(MacroTask.class::isInstance).map(MacroTask.class::cast).
                 orElseThrow(() -> new NotFoundException("MacroTask " + key));
 
         securityChecks(IdRepoEntitlement.TASK_READ, 
task.getRealm().getFullPath());
 
-        return binder.getMacroTaskForm(task);
+        return binder.getMacroTaskForm(task, locale);
     }
 
     protected ExecTO doExecute(
diff --git 
a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/TaskServiceImpl.java
 
b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/TaskServiceImpl.java
index b27bcefeb5..29d3d259a7 100644
--- 
a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/TaskServiceImpl.java
+++ 
b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/TaskServiceImpl.java
@@ -21,14 +21,18 @@ package org.apache.syncope.core.rest.cxf.service;
 import java.net.URI;
 import java.time.OffsetDateTime;
 import java.util.List;
+import java.util.Locale;
 import javax.ws.rs.BadRequestException;
 import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.LocaleUtils;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.form.SyncopeForm;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.SchedTaskTO;
 import org.apache.syncope.common.lib.to.TaskTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
@@ -114,8 +118,19 @@ public class TaskServiceImpl extends 
AbstractExecutableService implements TaskSe
     }
 
     @Override
-    public SyncopeForm getMacroTaskForm(final String key) {
-        return logic.getMacroTaskForm(key);
+    public SyncopeForm getMacroTaskForm(final String key, final String locale) 
{
+        Locale localeObj = null;
+        try {
+            localeObj = LocaleUtils.toLocale(locale);
+        } catch (Exception e) {
+            LOG.error("While attempting to convert {} to Locale", locale, e);
+
+            SyncopeClientException sce = 
SyncopeClientException.build(ClientExceptionType.InvalidEntity);
+            sce.getElements().add("Invalid locale '" + locale + "'");
+            throw sce;
+        }
+
+        return logic.getMacroTaskForm(key, localeObj);
     }
 
     @Override
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/task/FormPropertyDef.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/task/FormPropertyDef.java
index 518ab750d7..551990a886 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/task/FormPropertyDef.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/task/FormPropertyDef.java
@@ -18,12 +18,14 @@
  */
 package org.apache.syncope.core.persistence.api.entity.task;
 
+import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 import java.util.regex.Pattern;
 import org.apache.syncope.common.lib.form.FormPropertyType;
-import org.apache.syncope.core.persistence.api.entity.ProvidedKeyEntity;
+import org.apache.syncope.core.persistence.api.entity.Entity;
 
-public interface FormPropertyDef extends ProvidedKeyEntity {
+public interface FormPropertyDef extends Entity {
 
     MacroTask getMacroTask();
 
@@ -33,6 +35,10 @@ public interface FormPropertyDef extends ProvidedKeyEntity {
 
     void setName(String name);
 
+    Optional<String> getLabel(Locale locale);
+
+    Map<Locale, String> getLabels();
+
     FormPropertyType getType();
 
     void setType(FormPropertyType type);
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
index a2b64b64da..0230673fd2 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
@@ -54,6 +54,7 @@ import 
org.apache.syncope.core.persistence.api.entity.task.Task;
 import org.apache.syncope.core.persistence.api.entity.task.TaskUtils;
 import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
 import org.apache.syncope.core.persistence.jpa.entity.task.JPAMacroTask;
+import org.apache.syncope.core.persistence.jpa.entity.task.JPAMacroTaskCommand;
 import org.apache.syncope.core.persistence.jpa.entity.task.JPANotificationTask;
 import org.apache.syncope.core.persistence.jpa.entity.task.JPAPropagationTask;
 import 
org.apache.syncope.core.persistence.jpa.entity.task.JPAPropagationTaskExec;
@@ -198,8 +199,9 @@ public class JPATaskDAO extends AbstractDAO<Task<?>> 
implements TaskDAO {
 
     @Override
     public List<MacroTask> findByCommand(final Implementation command) {
-        TypedQuery<MacroTask> query = entityManager().createQuery("SELECT e 
FROM " + JPAMacroTask.class.getSimpleName()
-                + " e WHERE :command MEMBER OF e.commands", MacroTask.class);
+        TypedQuery<MacroTask> query = entityManager().createQuery(
+                "SELECT e.macroTask FROM " + 
JPAMacroTaskCommand.class.getSimpleName() + " e "
+                + "WHERE e.command=:command", MacroTask.class);
         query.setParameter("command", command);
 
         return query.getResultList();
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/JPAFormPropertyDef.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/JPAFormPropertyDef.java
index 5581675a43..99988194c0 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/JPAFormPropertyDef.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/JPAFormPropertyDef.java
@@ -19,6 +19,8 @@
 package org.apache.syncope.core.persistence.jpa.entity.task;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.regex.Pattern;
@@ -27,25 +29,36 @@ import javax.persistence.EnumType;
 import javax.persistence.Enumerated;
 import javax.persistence.Lob;
 import javax.persistence.ManyToOne;
+import javax.persistence.PostLoad;
+import javax.persistence.PostPersist;
+import javax.persistence.PostUpdate;
+import javax.persistence.PrePersist;
+import javax.persistence.PreUpdate;
 import javax.persistence.Table;
+import javax.persistence.Transient;
 import javax.validation.constraints.NotNull;
 import org.apache.syncope.common.lib.form.FormPropertyType;
 import org.apache.syncope.core.persistence.api.entity.task.FormPropertyDef;
 import org.apache.syncope.core.persistence.api.entity.task.MacroTask;
-import 
org.apache.syncope.core.persistence.jpa.entity.AbstractProvidedKeyEntity;
+import 
org.apache.syncope.core.persistence.jpa.entity.AbstractGeneratedKeyEntity;
 import 
org.apache.syncope.core.persistence.jpa.validation.entity.FormPropertyDefCheck;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 
 @Entity
 @Table(name = JPAFormPropertyDef.TABLE)
 @FormPropertyDefCheck
-public class JPAFormPropertyDef extends AbstractProvidedKeyEntity implements 
FormPropertyDef {
+public class JPAFormPropertyDef extends AbstractGeneratedKeyEntity implements 
FormPropertyDef {
 
     private static final long serialVersionUID = -5839990371546587373L;
 
     public static final String TABLE = "FormPropertyDef";
 
-    protected static final TypeReference<Map<String, String>> TYPEREF = new 
TypeReference<Map<String, String>>() {
+    protected static final TypeReference<Map<String, String>> 
ENUMVALUES_TYPEREF =
+            new TypeReference<Map<String, String>>() {
+    };
+
+    protected static final TypeReference<HashMap<Locale, String>> 
LABEL_TYPEREF =
+            new TypeReference<HashMap<Locale, String>>() {
     };
 
     private int idx;
@@ -56,6 +69,12 @@ public class JPAFormPropertyDef extends 
AbstractProvidedKeyEntity implements For
     @NotNull
     private String name;
 
+    @Lob
+    private String labels;
+
+    @Transient
+    private Map<Locale, String> labelMap = new HashMap<>();
+
     @NotNull
     @Enumerated(EnumType.STRING)
     private FormPropertyType type;
@@ -107,6 +126,16 @@ public class JPAFormPropertyDef extends 
AbstractProvidedKeyEntity implements For
         this.name = name;
     }
 
+    @Override
+    public Optional<String> getLabel(final Locale locale) {
+        return Optional.ofNullable(labelMap.get(locale));
+    }
+
+    @Override
+    public Map<Locale, String> getLabels() {
+        return labelMap;
+    }
+
     @Override
     public FormPropertyType getType() {
         return type;
@@ -169,7 +198,7 @@ public class JPAFormPropertyDef extends 
AbstractProvidedKeyEntity implements For
 
     @Override
     public Map<String, String> getEnumValues() {
-        return Optional.ofNullable(enumValues).map(v -> 
POJOHelper.deserialize(v, TYPEREF)).orElse(Map.of());
+        return Optional.ofNullable(enumValues).map(v -> 
POJOHelper.deserialize(v, ENUMVALUES_TYPEREF)).orElse(Map.of());
     }
 
     @Override
@@ -196,4 +225,30 @@ public class JPAFormPropertyDef extends 
AbstractProvidedKeyEntity implements For
     public void setDropdownFreeForm(final boolean dropdownFreeForm) {
         this.dropdownFreeForm = dropdownFreeForm;
     }
+
+    protected void json2map(final boolean clearFirst) {
+        if (clearFirst) {
+            getLabels().clear();
+        }
+        if (labels != null) {
+            getLabels().putAll(POJOHelper.deserialize(labels, LABEL_TYPEREF));
+        }
+    }
+
+    @PostLoad
+    public void postLoad() {
+        json2map(false);
+    }
+
+    @PostPersist
+    @PostUpdate
+    public void postSave() {
+        json2map(true);
+    }
+
+    @PrePersist
+    @PreUpdate
+    public void map2json() {
+        labels = POJOHelper.serialize(getLabels());
+    }
 }
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
index 6f55bca310..4edb9705f1 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
@@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.fail;
 
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -195,8 +196,8 @@ public class TaskTest extends AbstractTest {
         task.add(macroTaskCommand);
 
         FormPropertyDef formPropertyDef = 
entityFactory.newEntity(FormPropertyDef.class);
-        formPropertyDef.setKey("one");
-        formPropertyDef.setName("One");
+        formPropertyDef.setName("one");
+        formPropertyDef.getLabels().put(Locale.ENGLISH, "One");
         formPropertyDef.setType(FormPropertyType.Enum);
         task.add(formPropertyDef);
 
@@ -222,6 +223,7 @@ public class TaskTest extends AbstractTest {
         assertEquals(1, task.getCommands().size());
         assertEquals(command, task.getCommands().get(0).getCommand());
         assertEquals(1, task.getFormPropertyDefs().size());
+        assertNotNull(task.getFormPropertyDefs().get(0).getKey());
         assertEquals(formPropertyDef, task.getFormPropertyDefs().get(0));
 
         MacroTask actual = (MacroTask) taskDAO.find(TaskType.MACRO, 
task.getKey());
diff --git 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/TaskDataBinder.java
 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/TaskDataBinder.java
index a8a1dc56af..464437ac1a 100644
--- 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/TaskDataBinder.java
+++ 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/TaskDataBinder.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.api.data;
 
+import java.util.Locale;
 import org.apache.syncope.common.lib.form.SyncopeForm;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.SchedTaskTO;
@@ -40,5 +41,5 @@ public interface TaskDataBinder {
 
     <T extends TaskTO> T getTaskTO(Task<?> task, TaskUtils taskUtil, boolean 
details);
 
-    SyncopeForm getMacroTaskForm(MacroTask task);
+    SyncopeForm getMacroTaskForm(MacroTask task, Locale locale);
 }
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
index 87c0a7fd04..26689efb69 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.java.data;
 
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -280,8 +281,8 @@ public class TaskDataBinderImpl extends 
AbstractExecutableDatabinder implements
         macroTask.getFormPropertyDefs().clear();
         macroTaskTO.getFormPropertyDefs().forEach(fpdTO -> {
             FormPropertyDef fpd = 
entityFactory.newEntity(FormPropertyDef.class);
-            fpd.setKey(fpdTO.getKey());
             fpd.setName(fpdTO.getName());
+            fpd.getLabels().putAll(fpdTO.getLabels());
             fpd.setType(fpdTO.getType());
             fpd.setReadable(fpdTO.isReadable());
             fpd.setWritable(fpdTO.isWritable());
@@ -515,6 +516,7 @@ public class TaskDataBinderImpl extends 
AbstractExecutableDatabinder implements
                     FormPropertyDefTO fpdTO = new FormPropertyDefTO();
                     fpdTO.setKey(fpd.getKey());
                     fpdTO.setName(fpd.getName());
+                    fpdTO.getLabels().putAll(fpd.getLabels());
                     fpdTO.setType(fpd.getType());
                     fpdTO.setReadable(fpd.isReadable());
                     fpdTO.setWritable(fpd.isWritable());
@@ -596,7 +598,7 @@ public class TaskDataBinderImpl extends 
AbstractExecutableDatabinder implements
     }
 
     @Override
-    public SyncopeForm getMacroTaskForm(final MacroTask task) {
+    public SyncopeForm getMacroTaskForm(final MacroTask task, final Locale 
locale) {
         if (task.getFormPropertyDefs().isEmpty()) {
             throw new NotFoundException("No form properties defined for 
MacroTask " + task.getKey());
         }
@@ -623,13 +625,13 @@ public class TaskDataBinderImpl extends 
AbstractExecutableDatabinder implements
 
         
form.getProperties().addAll(task.getFormPropertyDefs().stream().map(fpd -> {
             FormProperty prop = new FormProperty();
-            prop.setId(fpd.getKey());
-            prop.setName(fpd.getName());
+            prop.setId(fpd.getName());
+            prop.setName(fpd.getLabels().getOrDefault(locale, fpd.getName()));
             prop.setReadable(fpd.isReadable());
             prop.setRequired(fpd.isRequired());
             prop.setWritable(fpd.isWritable());
             prop.setType(fpd.getType());
-            actions.flatMap(a -> a.getDefaultValue(fpd.getKey())).ifPresent(v 
-> prop.setValue(v));
+            actions.flatMap(a -> a.getDefaultValue(fpd.getName())).ifPresent(v 
-> prop.setValue(v));
             switch (prop.getType()) {
                 case String:
                     prop.setStringRegEx(fpd.getStringRegEx());
@@ -640,13 +642,12 @@ public class TaskDataBinderImpl extends 
AbstractExecutableDatabinder implements
                     break;
 
                 case Enum:
-                    fpd.getEnumValues().
-                            forEach((key, value) -> 
prop.getEnumValues().add(new FormPropertyValue(key, value)));
+                    fpd.getEnumValues().forEach((k, v) -> 
prop.getEnumValues().add(new FormPropertyValue(k, v)));
                     break;
 
                 case Dropdown:
-                    actions.ifPresent(a -> a.getDropdownValues(fpd.getKey()).
-                            forEach((key, value) -> 
prop.getDropdownValues().add(new FormPropertyValue(key, value))));
+                    actions.ifPresent(a -> a.getDropdownValues(fpd.getName()).
+                            forEach((k, v) -> prop.getDropdownValues().add(new 
FormPropertyValue(k, v))));
                     
prop.setDropdownSingleSelection(fpd.isDropdownSingleSelection());
                     prop.setDropdownFreeForm(fpd.isDropdownFreeForm());
                     break;
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/MacroJobDelegate.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/MacroJobDelegate.java
index cbab193433..0718efc70b 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/MacroJobDelegate.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/MacroJobDelegate.java
@@ -92,8 +92,8 @@ public class MacroJobDelegate extends 
AbstractSchedTaskJobDelegate<MacroTask> {
         Set<String> missingFormProperties = 
task.getFormPropertyDefs().stream().
                 filter(FormPropertyDef::isRequired).
                 map(fpd -> Pair.of(
-                fpd.getKey(),
-                macroTaskForm.getProperty(fpd.getKey()).map(p -> p.getValue() 
!= null))).
+                fpd.getName(),
+                macroTaskForm.getProperty(fpd.getName()).map(p -> p.getValue() 
!= null))).
                 filter(pair -> pair.getRight().isEmpty()).
                 map(Pair::getLeft).
                 collect(Collectors.toSet());
@@ -104,7 +104,7 @@ public class MacroJobDelegate extends 
AbstractSchedTaskJobDelegate<MacroTask> {
         // build the JEXL context where variables are mapped to property 
values, built according to the defined type
         Map<String, Object> vars = new HashMap<>();
         for (FormPropertyDef fpd : task.getFormPropertyDefs()) {
-            String value = 
macroTaskForm.getProperty(fpd.getKey()).map(FormProperty::getValue).orElse(null);
+            String value = 
macroTaskForm.getProperty(fpd.getName()).map(FormProperty::getValue).orElse(null);
             if (value == null) {
                 continue;
             }
@@ -115,40 +115,40 @@ public class MacroJobDelegate extends 
AbstractSchedTaskJobDelegate<MacroTask> {
                             map(pattern -> !pattern.matcher(value).matches()).
                             orElse(false)) {
 
-                        throw new JobExecutionException("RegEx not matching 
for " + fpd.getKey() + ": " + value);
+                        throw new JobExecutionException("RegEx not matching 
for " + fpd.getName() + ": " + value);
                     }
 
-                    vars.put(fpd.getKey(), value);
+                    vars.put(fpd.getName(), value);
                     break;
 
                 case Password:
-                    vars.put(fpd.getKey(), value);
+                    vars.put(fpd.getName(), value);
                     break;
 
                 case Boolean:
-                    vars.put(fpd.getKey(), BooleanUtils.toBoolean(value));
+                    vars.put(fpd.getName(), BooleanUtils.toBoolean(value));
                     break;
 
                 case Date:
                     try {
-                        vars.put(fpd.getKey(), 
StringUtils.isBlank(fpd.getDatePattern())
+                        vars.put(fpd.getName(), 
StringUtils.isBlank(fpd.getDatePattern())
                                 ? FormatUtils.parseDate(value)
                                 : FormatUtils.parseDate(value, 
fpd.getDatePattern()));
                     } catch (DateTimeParseException e) {
-                        throw new JobExecutionException("Unparseable date " + 
fpd.getKey() + ": " + value, e);
+                        throw new JobExecutionException("Unparseable date " + 
fpd.getName() + ": " + value, e);
                     }
                     break;
 
                 case Long:
-                    vars.put(fpd.getKey(), NumberUtils.toLong(value));
+                    vars.put(fpd.getName(), NumberUtils.toLong(value));
                     break;
 
                 case Enum:
                     if (!fpd.getEnumValues().containsKey(value)) {
-                        throw new JobExecutionException("Not allowed for " + 
fpd.getKey() + ": " + value);
+                        throw new JobExecutionException("Not allowed for " + 
fpd.getName() + ": " + value);
                     }
 
-                    vars.put(fpd.getKey(), value);
+                    vars.put(fpd.getName(), value);
                     break;
 
                 case Dropdown:
@@ -157,14 +157,14 @@ public class MacroJobDelegate extends 
AbstractSchedTaskJobDelegate<MacroTask> {
                                 ? List.of(value)
                                 : List.of(value.split(";"));
 
-                        if (!actions.map(a -> 
a.getDropdownValues(fpd.getKey()).keySet()).
+                        if (!actions.map(a -> 
a.getDropdownValues(fpd.getName()).keySet()).
                                 orElse(Set.of()).containsAll(values)) {
 
-                            throw new JobExecutionException("Not allowed for " 
+ fpd.getKey() + ": " + values);
+                            throw new JobExecutionException("Not allowed for " 
+ fpd.getName() + ": " + values);
                         }
                     }
 
-                    vars.put(fpd.getKey(), value);
+                    vars.put(fpd.getName(), value);
                     break;
 
                 default:
@@ -311,7 +311,9 @@ public class MacroJobDelegate extends 
AbstractSchedTaskJobDelegate<MacroTask> {
 
                     throw new JobExecutionException(
                             "While running " + command.getKey(),
-                            new 
IllegalArgumentException(args.getClass().getName()));
+                            new IllegalArgumentException(violations.stream().
+                                    map(v -> v.getPropertyPath() + ": " + 
v.getMessage()).
+                                    collect(Collectors.joining(","))));
                 }
             }
 
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroTaskITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroTaskITCase.java
index 315b2ec1df..d692c320d4 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroTaskITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroTaskITCase.java
@@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import java.nio.charset.StandardCharsets;
+import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 import javax.ws.rs.core.Response;
 import org.apache.commons.io.IOUtils;
@@ -130,16 +131,16 @@ public class MacroTaskITCase extends AbstractITCase {
                                 new 
CommandTO.Builder(TestCommand.class.getSimpleName()).args(TCA).build());
 
                         FormPropertyDefTO realm = new FormPropertyDefTO();
-                        realm.setKey("realm");
-                        realm.setName("Realm");
+                        realm.setName("realm");
+                        realm.getLabels().put(Locale.ENGLISH, "Realm");
                         realm.setWritable(true);
                         realm.setRequired(true);
                         realm.setType(FormPropertyType.String);
                         task.getFormPropertyDefs().add(realm);
 
                         FormPropertyDefTO parent = new FormPropertyDefTO();
-                        parent.setKey("parent");
-                        parent.setName("Parent Realm");
+                        parent.setName("parent");
+                        parent.getLabels().put(Locale.ENGLISH, "Parent Realm");
                         parent.setWritable(true);
                         parent.setRequired(true);
                         parent.setType(FormPropertyType.Dropdown);
@@ -166,7 +167,7 @@ public class MacroTaskITCase extends AbstractITCase {
 
     @Test
     public void execute() {
-        SyncopeForm form = TASK_SERVICE.getMacroTaskForm(MACRO_TASK_KEY);
+        SyncopeForm form = TASK_SERVICE.getMacroTaskForm(MACRO_TASK_KEY, 
Locale.ENGLISH.toLanguageTag());
         form.getProperty("realm").orElseThrow().setValue("macro");
         FormProperty parent = form.getProperty("parent").orElseThrow();
         assertTrue(parent.getDropdownValues().stream().anyMatch(v -> 
"/odd".equals(v.getKey())));


Reply via email to