[SYNCOPE-1639] Replacing HashSet with TreeSet + Comparable
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDetails.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDetails.java
index 5911ecc..937279c 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDetails.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDetails.java
@@ -18,10 +18,23 @@
  */
 package org.apache.syncope.client.console.audit;
 
+import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.StreamReadFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import java.io.IOException;
 import java.io.Serializable;
 import java.util.Date;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.JsonDiffPanel;
@@ -44,7 +57,68 @@
 
     private static final Logger LOG = LoggerFactory.getLogger(AuditHistoryDetails.class);
 
-    private static final ObjectMapper MAPPER = new ObjectMapper();
+    private static class SortingNodeFactory extends JsonNodeFactory {
+
+        private static final long serialVersionUID = 1870252010670L;
+
+        @Override
+        public ObjectNode objectNode() {
+            return new ObjectNode(this, new TreeMap<>());
+        }
+    }
+
+    private static class SortedSetJsonSerializer extends StdSerializer<Set<?>> {
+
+        private static final long serialVersionUID = 3849059774309L;
+
+        SortedSetJsonSerializer(final Class<Set<?>> clazz) {
+            super(clazz);
+        }
+
+        @Override
+        public void serialize(
+                final Set<?> set,
+                final JsonGenerator gen,
+                final SerializerProvider sp) throws IOException {
+
+            if (set == null) {
+                gen.writeNull();
+                return;
+            }
+
+            gen.writeStartArray();
+
+            if (!set.isEmpty()) {
+                Set<?> sorted = set;
+
+                // create sorted set only if it itself is not already SortedSet
+                if (!SortedSet.class.isAssignableFrom(set.getClass())) {
+                    Object item = set.iterator().next();
+                    if (Comparable.class.isAssignableFrom(item.getClass())) {
+                        // and only if items are Comparable
+                        sorted = new TreeSet<>(set);
+                    } else {
+                        LOG.debug("Cannot sort items of type {}", item.getClass());
+                    }
+                }
+
+                for (Object item : sorted) {
+                    gen.writeObject(item);
+                }
+            }
+
+            gen.writeEndArray();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> Class<T> cast(final Class<?> aClass) {
+        return (Class<T>) aClass;
+    }
+
+    private static final ObjectMapper MAPPER = JsonMapper.builder().
+            nodeFactory(new SortingNodeFactory()).build().
+            registerModule(new SimpleModule().addSerializer(new SortedSetJsonSerializer(cast(Set.class))));
 
     public AuditHistoryDetails(
             final MultilevelPanel mlp,
@@ -114,7 +188,9 @@
                     ? MAPPER.readTree(auditEntry.getOutput()).get("entity").toPrettyString()
                     : auditEntry.getBefore();
 
-            T entity = MAPPER.readValue(content, reference);
+            T entity = MAPPER.reader().
+                    with(StreamReadFeature.STRICT_DUPLICATE_DETECTION).
+                    readValue(content, reference);
             if (entity instanceof UserTO) {
                 UserTO userTO = (UserTO) entity;
                 userTO.setPassword(null);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java
index 8c5b80e..2ebc7bc 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java
@@ -30,10 +30,6 @@
 
     private static final long serialVersionUID = -5110368813584745668L;
 
-    private final IModel<String> first;
-
-    private final IModel<String> second;
-
     public JsonDiffPanel(
             final BaseModal<String> modal,
             final IModel<String> first,
@@ -41,13 +37,13 @@
             final PageReference pageRef) {
 
         super(modal, pageRef);
-        this.second = second;
-        this.first = first;
-        TextArea<String> jsonEditorInfoDefArea1 = new TextArea<>("jsonEditorInfo1", this.first);
-        TextArea<String> jsonEditorInfoDefArea2 = new TextArea<>("jsonEditorInfo2", this.second);
+
+        TextArea<String> jsonEditorInfoDefArea1 = new TextArea<>("jsonEditorInfo1", first);
         jsonEditorInfoDefArea1.setMarkupId("jsonEditorInfo1").setOutputMarkupPlaceholderTag(true);
-        jsonEditorInfoDefArea2.setMarkupId("jsonEditorInfo2").setOutputMarkupPlaceholderTag(true);
         add(jsonEditorInfoDefArea1);
+
+        TextArea<String> jsonEditorInfoDefArea2 = new TextArea<>("jsonEditorInfo2", second);
+        jsonEditorInfoDefArea2.setMarkupId("jsonEditorInfo2").setOutputMarkupPlaceholderTag(true);
         add(jsonEditorInfoDefArea2);
     }
 
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/Attr.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/Attr.java
index a555bff..5b920ff 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/Attr.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/Attr.java
@@ -25,10 +25,11 @@
 import java.util.Collection;
 import java.util.List;
 import javax.ws.rs.PathParam;
+import org.apache.commons.lang3.builder.CompareToBuilder;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 
-public class Attr implements BaseBean {
+public class Attr implements Comparable<Attr>, BaseBean {
 
     private static final long serialVersionUID = 4941691338796323623L;
 
@@ -98,6 +99,16 @@
     }
 
     @Override
+    public int compareTo(final Attr other) {
+        return equals(other)
+                ? 0
+                : new CompareToBuilder().
+                        append(schema, other.schema).
+                        append(values, other.values).
+                        toComparison();
+    }
+
+    @Override
     public int hashCode() {
         return new HashCodeBuilder().
                 append(schema).
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyTO.java
index 942ffc0..bdc9a8a 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyTO.java
@@ -28,7 +28,7 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.HashSet;
+import java.util.TreeSet;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -82,15 +82,15 @@
 
     private String status;
 
-    private final Set<String> auxClasses = new HashSet<>();
+    private final Set<String> auxClasses = new TreeSet<>();
 
-    private final Set<Attr> plainAttrs = new HashSet<>();
+    private final Set<Attr> plainAttrs = new TreeSet<>();
 
-    private final Set<Attr> derAttrs = new HashSet<>();
+    private final Set<Attr> derAttrs = new TreeSet<>();
 
-    private final Set<Attr> virAttrs = new HashSet<>();
+    private final Set<Attr> virAttrs = new TreeSet<>();
 
-    private final Set<String> resources = new HashSet<>();
+    private final Set<String> resources = new TreeSet<>();
 
     @Schema(name = "_class", required = true)
     public abstract String getDiscriminator();
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/ConnObjectTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/ConnObjectTO.java
index 9b72672..921364d 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/ConnObjectTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/ConnObjectTO.java
@@ -21,9 +21,9 @@
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
-import java.util.LinkedHashSet;
 import java.util.Optional;
 import java.util.Set;
+import java.util.TreeSet;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.Attr;
@@ -35,7 +35,7 @@
 
     private String fiql;
 
-    private final Set<Attr> attrs = new LinkedHashSet<>();
+    private final Set<Attr> attrs = new TreeSet<>();
 
     public String getFiql() {
         return fiql;
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/DelegationTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/DelegationTO.java
index 3d567c2..36c75b7 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/DelegationTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/DelegationTO.java
@@ -22,8 +22,8 @@
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.Date;
-import java.util.HashSet;
 import java.util.Set;
+import java.util.TreeSet;
 import javax.ws.rs.PathParam;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
@@ -39,7 +39,7 @@
 
     private String delegated;
 
-    private final Set<String> roles = new HashSet<>();
+    private final Set<String> roles = new TreeSet<>();
 
     @Schema(accessMode = Schema.AccessMode.READ_ONLY)
     @Override
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/LinkedAccountTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/LinkedAccountTO.java
index 72dd158..1d149da 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/LinkedAccountTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/LinkedAccountTO.java
@@ -21,9 +21,9 @@
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
-import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
+import java.util.TreeSet;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.Attr;
@@ -78,9 +78,9 @@
 
     private boolean suspended;
 
-    private final Set<Attr> plainAttrs = new HashSet<>();
+    private final Set<Attr> plainAttrs = new TreeSet<>();
 
-    private final Set<String> privileges = new HashSet<>();
+    private final Set<String> privileges = new TreeSet<>();
 
     @Override
     public String getKey() {
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/MembershipTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/MembershipTO.java
index 591f2ad..72477b2 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/MembershipTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/MembershipTO.java
@@ -26,10 +26,10 @@
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.TreeSet;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.BaseBean;
@@ -90,11 +90,11 @@
 
     private String groupName;
 
-    private final Set<Attr> plainAttrs = new HashSet<>();
+    private final Set<Attr> plainAttrs = new TreeSet<>();
 
-    private final Set<Attr> derAttrs = new HashSet<>();
+    private final Set<Attr> derAttrs = new TreeSet<>();
 
-    private final Set<Attr> virAttrs = new HashSet<>();
+    private final Set<Attr> virAttrs = new TreeSet<>();
 
     public String getGroupKey() {
         return groupKey;
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTaskTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTaskTO.java
index 18bc4ad..1036ed5 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTaskTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTaskTO.java
@@ -22,8 +22,8 @@
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
-import java.util.HashSet;
 import java.util.Set;
+import java.util.TreeSet;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
@@ -40,7 +40,7 @@
 
     private String entityKey;
 
-    private final Set<String> recipients = new HashSet<>();
+    private final Set<String> recipients = new TreeSet<>();
 
     private String sender;
 
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java
index 6e7158f..7673f17 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java
@@ -22,10 +22,8 @@
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import javax.ws.rs.PathParam;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
@@ -56,7 +54,7 @@
 
     private final Map<String, AnyTO> templates = new HashMap<>();
 
-    private final Set<String> resources = new HashSet<>();
+    private final List<String> resources = new ArrayList<>();
 
     @Override
     public String getKey() {
@@ -148,7 +146,7 @@
 
     @JacksonXmlElementWrapper(localName = "resources")
     @JacksonXmlProperty(localName = "resource")
-    public Set<String> getResources() {
+    public List<String> getResources() {
         return resources;
     }
 
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java
index 3be4d55..bf4b22e 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java
@@ -21,9 +21,9 @@
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.TreeSet;
 import javax.ws.rs.PathParam;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
@@ -34,7 +34,7 @@
 
     private String key;
 
-    private final Set<String> entitlements = new HashSet<>();
+    private final Set<String> entitlements = new TreeSet<>();
 
     private final List<String> realms = new ArrayList<>();
 
@@ -42,7 +42,7 @@
 
     private String dynMembershipCond;
 
-    private final Set<String> privileges = new HashSet<>();
+    private final Set<String> privileges = new TreeSet<>();
 
     @Override
     public String getKey() {
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
index 6b46f70..0417837 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
@@ -25,10 +25,10 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.TreeSet;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
@@ -70,7 +70,7 @@
 
     private final List<String> dynRoles = new ArrayList<>();
 
-    private final Set<String> privileges = new HashSet<>();
+    private final Set<String> privileges = new TreeSet<>();
 
     private final List<LinkedAccountTO> linkedAccounts = new ArrayList<>();