You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2015/01/08 14:17:22 UTC

[02/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationTaskExecutor.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationTaskExecutor.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationTaskExecutor.java
new file mode 100644
index 0000000..1b37718
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationTaskExecutor.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.provisioning.api.propagation;
+
+import java.util.Collection;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.persistence.api.entity.task.TaskExec;
+
+/**
+ * Execute propagation tasks.
+ *
+ * @see PropagationTask
+ */
+public interface PropagationTaskExecutor {
+
+    /**
+     * Name for special propagation attribute used to indicate whether there are attributes, marked as mandatory in the
+     * mapping but not to be propagated.
+     */
+    String MANDATORY_MISSING_ATTR_NAME = "__MANDATORY_MISSING__";
+
+    /**
+     * Name for special propagation attribute used to indicate whether there are attributes, marked as mandatory in the
+     * mapping but about to be propagated as null or empty.
+     */
+    String MANDATORY_NULL_OR_EMPTY_ATTR_NAME = "__MANDATORY_NULL_OR_EMPTY__";
+
+    /**
+     * Execute the given PropagationTask and returns the generated TaskExec.
+     *
+     * @param task to be executed
+     * @return the generated TaskExec
+     */
+    TaskExec execute(PropagationTask task);
+
+    /**
+     * Execute the given PropagationTask, invoke the given handler and returns the generated TaskExec.
+     *
+     * @param task to be executed
+     * @param reporter to report propagation execution status
+     * @return the generated TaskExec
+     */
+    TaskExec execute(PropagationTask task, PropagationReporter reporter);
+
+    /**
+     * Execute a collection of PropagationTask objects.
+     * The process is interrupted as soon as the result of the communication with a primary resource is in error.
+     *
+     * @param tasks to be executed
+     */
+    void execute(Collection<PropagationTask> tasks);
+
+    /**
+     * Execute a collection of PropagationTask objects and invoke the given handler on each of these.
+     * The process is interrupted as soon as the result of the communication with a primary resource is in error.
+     *
+     * @param tasks to be execute, in given order
+     * @param reporter to report propagation execution status
+     */
+    void execute(Collection<PropagationTask> tasks, PropagationReporter reporter);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/sync/SyncCorrelationRule.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/sync/SyncCorrelationRule.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/sync/SyncCorrelationRule.java
new file mode 100644
index 0000000..4056e74
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/sync/SyncCorrelationRule.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.provisioning.api.sync;
+
+import org.apache.syncope.persistence.api.dao.search.SearchCond;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+
+/**
+ * Interface for correlation rule to be evaluated during SyncJob execution.
+ */
+public interface SyncCorrelationRule {
+
+    /**
+     * Return a search condition.
+     *
+     * @param connObj connector object.
+     * @return search condition.
+     */
+    SearchCond getSearchCond(ConnectorObject connObj);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-common/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-common/pom.xml b/syncope620/server/provisioning-common/pom.xml
new file mode 100644
index 0000000..d920258
--- /dev/null
+++ b/syncope620/server/provisioning-common/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.syncope</groupId>
+    <artifactId>syncope-server</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+
+  <name>Apache Syncope Server Provisioning Common</name>
+  <description>Apache Syncope Server Provisioning Common</description>
+  <groupId>org.apache.syncope.server</groupId>
+  <artifactId>syncope-provisioning-common</artifactId>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-provisioning-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/DisabledVirAttrCache.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/DisabledVirAttrCache.java b/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/DisabledVirAttrCache.java
new file mode 100644
index 0000000..3491075
--- /dev/null
+++ b/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/DisabledVirAttrCache.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.provisioning.common.cache;
+
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.provisioning.api.cache.VirAttrCacheValue;
+
+/**
+ * Empty virtual attribute value cache implementation.
+ */
+public class DisabledVirAttrCache implements VirAttrCache {
+
+    @Override
+    public void expire(final AttributableType type, final Long id, final String schemaName) {
+        // nothing to do
+    }
+
+    @Override
+    public VirAttrCacheValue get(final AttributableType type, final Long id, final String schemaName) {
+        return null;
+    }
+
+    @Override
+    public boolean isValidEntry(VirAttrCacheValue value) {
+        return false;
+    }
+
+    @Override
+    public void put(
+            final AttributableType type, final Long id, final String schemaName, final VirAttrCacheValue value) {
+
+        // nothing to do
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/MemoryVirAttrCache.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/MemoryVirAttrCache.java b/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/MemoryVirAttrCache.java
new file mode 100644
index 0000000..0199499
--- /dev/null
+++ b/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/MemoryVirAttrCache.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.provisioning.common.cache;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.provisioning.api.cache.VirAttrCacheKey;
+import org.apache.syncope.provisioning.api.cache.VirAttrCacheValue;
+
+/**
+ * In-memory (HashMap) virtual attribute value cache implementation.
+ */
+public class MemoryVirAttrCache implements VirAttrCache {
+
+    /**
+     * Elapsed time in seconds.
+     */
+    protected int ttl;
+
+    /**
+     * Max cache size.
+     */
+    protected int maxCacheSize;
+
+    /**
+     * Cache entries.
+     */
+    protected final Map<VirAttrCacheKey, VirAttrCacheValue> cache = new HashMap<VirAttrCacheKey, VirAttrCacheValue>();
+
+    public MemoryVirAttrCache(final int ttl, final int maxCacheSize) {
+        this.ttl = ttl;
+        this.maxCacheSize = maxCacheSize;
+    }
+
+    /**
+     * Cache virtual attribute values.
+     *
+     * @param type user or role
+     * @param id user or role id
+     * @param schemaName virtual attribute name
+     * @param value virtual attribute values
+     */
+    @Override
+    public void put(
+            final AttributableType type,
+            final Long id,
+            final String schemaName,
+            final VirAttrCacheValue value) {
+
+        synchronized (cache) {
+            // this operations (retrieve cache space and put entry on) have to be thread safe.
+            if (this.cache.size() >= this.maxCacheSize) {
+                free();
+            }
+
+            cache.put(new VirAttrCacheKey(type, id, schemaName), value);
+        }
+    }
+
+    /**
+     * Retrieve cached value. Return null in case of virtual attribute not cached.
+     *
+     * @param type user or role
+     * @param id user or role id
+     * @param schemaName virtual attribute schema name.
+     * @return cached values or null if virtual attribute is not cached.
+     */
+    @Override
+    public VirAttrCacheValue get(final AttributableType type, final Long id, final String schemaName) {
+        return cache.get(new VirAttrCacheKey(type, id, schemaName));
+    }
+
+    /**
+     * Force entry expiring.
+     *
+     * @param type user or role
+     * @param id user or role id
+     * @param schemaName virtual attribute schema name
+     */
+    @Override
+    public void expire(final AttributableType type, final Long id, final String schemaName) {
+        final VirAttrCacheValue value = cache.get(new VirAttrCacheKey(type, id, schemaName));
+        if (isValidEntry(value)) {
+            synchronized (cache) {
+                value.forceExpiring();
+            }
+        }
+    }
+
+    /**
+     * Remove expired entries if exist. If required, one entry at least (the latest recently used) will be taken off.
+     * This method is not thread safe: the caller have to take care to synchronize the call.
+     */
+    private void free() {
+        final Set<VirAttrCacheKey> toBeRemoved = new HashSet<VirAttrCacheKey>();
+
+        Map.Entry<VirAttrCacheKey, VirAttrCacheValue> latest = null;
+
+        for (Map.Entry<VirAttrCacheKey, VirAttrCacheValue> entry : cache.entrySet()) {
+            if (isValidEntry(entry.getValue())) {
+                final Date date = entry.getValue().getLastAccessDate();
+                if (latest == null || latest.getValue().getLastAccessDate().after(date)) {
+                    latest = entry;
+                }
+            } else {
+                toBeRemoved.add(entry.getKey());
+            }
+        }
+
+        if (toBeRemoved.isEmpty() && latest != null) {
+            // remove the oldest entry
+            cache.remove(latest.getKey());
+        } else {
+            // remove expired entries
+            cache.keySet().removeAll(toBeRemoved);
+        }
+    }
+
+    /**
+     * Cache entry is valid if and only if value exist and it is not expired.
+     *
+     * @param value cache entry value.
+     * @return TRUE if the value is valid; FALSE otherwise.
+     */
+    @Override
+    public boolean isValidEntry(final VirAttrCacheValue value) {
+        final Date expiringDate = new Date(value == null ? 0 : value.getCreationDate().getTime() + ttl * 1000);
+        return expiringDate.after(new Date());
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/security/src/main/java/org/apache/syncope/server/security/SecureRandomUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/security/src/main/java/org/apache/syncope/server/security/SecureRandomUtil.java b/syncope620/server/security/src/main/java/org/apache/syncope/server/security/SecureRandomUtil.java
deleted file mode 100644
index 3f2735a..0000000
--- a/syncope620/server/security/src/main/java/org/apache/syncope/server/security/SecureRandomUtil.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.server.security;
-
-import java.security.SecureRandom;
-
-import org.apache.commons.lang3.RandomStringUtils;
-
-public class SecureRandomUtil {
-    
-    private static final SecureRandom RANDOM = new SecureRandom();
-
-    public static String generateRandomPassword(final int tokenLength) {
-        return RandomStringUtils.random(tokenLength, 0, 0, true, false, null, RANDOM);
-    }
-    
-    public static String generateRandomLetter() {
-        return RandomStringUtils.random(1, 0, 0, true, false, null, RANDOM);
-    }
-    
-    public static String generateRandomNumber() {
-        return RandomStringUtils.random(1, 0, 0, false, true, null, RANDOM);
-    }
-    
-    public static String generateRandomSpecialCharacter(char[] characters) {
-        return RandomStringUtils.random(1, 0, 0, false, false, characters, RANDOM);
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/security/src/main/java/org/apache/syncope/server/security/UnauthorizedRoleException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/security/src/main/java/org/apache/syncope/server/security/UnauthorizedRoleException.java b/syncope620/server/security/src/main/java/org/apache/syncope/server/security/UnauthorizedRoleException.java
new file mode 100644
index 0000000..6586ccc
--- /dev/null
+++ b/syncope620/server/security/src/main/java/org/apache/syncope/server/security/UnauthorizedRoleException.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.security;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class UnauthorizedRoleException extends RuntimeException {
+
+    private static final long serialVersionUID = 7540587364235915081L;
+
+    private final Set<Long> roleIds;
+
+    public UnauthorizedRoleException(final Set<Long> roleIds) {
+        super("Missing entitlement for role(s) " + roleIds);
+        this.roleIds = roleIds;
+    }
+
+    public UnauthorizedRoleException(final Long roleId) {
+        this(Collections.singleton(roleId));
+    }
+
+    public Set<Long> getRoleIds() {
+        return roleIds;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/pom.xml b/syncope620/server/utils/pom.xml
index af4a55e..4e9fb6f 100644
--- a/syncope620/server/utils/pom.xml
+++ b/syncope620/server/utils/pom.xml
@@ -62,7 +62,7 @@ under the License.
 
     <dependency>
       <groupId>org.springframework</groupId>
-      <artifactId>spring-context</artifactId>
+      <artifactId>spring-tx</artifactId>
     </dependency>
 
     <dependency>
@@ -70,6 +70,21 @@ under the License.
       <artifactId>syncope-persistence-api</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-provisioning-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-server-security</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-server-spring</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 
 </project>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ConnObjectUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ConnObjectUtil.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ConnObjectUtil.java
new file mode 100644
index 0000000..d2afd35
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ConnObjectUtil.java
@@ -0,0 +1,764 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.AttributableOperations;
+import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ConnObjectTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.persistence.api.attrvalue.validation.ParsingValidationException;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+import org.apache.syncope.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.Connector;
+import org.apache.syncope.provisioning.api.ConnectorFactory;
+import org.apache.syncope.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.provisioning.api.cache.VirAttrCacheValue;
+import org.apache.syncope.server.security.Encryptor;
+import org.apache.syncope.server.security.UnauthorizedRoleException;
+import org.apache.syncope.server.spring.ApplicationContextProvider;
+import org.apache.syncope.server.utils.jexl.JexlUtil;
+import org.identityconnectors.common.Base64;
+import org.identityconnectors.common.security.GuardedByteArray;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.OperationOptions;
+import org.identityconnectors.framework.common.objects.Uid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class ConnObjectUtil {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(ConnObjectUtil.class);
+
+    @Autowired
+    private PolicyDAO policyDAO;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private RoleDAO roleDAO;
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private PlainSchemaDAO plainSchemaDAO;
+
+    @Autowired
+    private PasswordGenerator pwdGen;
+
+    private final Encryptor encryptor = Encryptor.getInstance();
+
+    /**
+     * Virtual attribute cache.
+     */
+    @Autowired
+    private VirAttrCache virAttrCache;
+
+    public ObjectClass fromSubject(final Subject<?, ?, ?> subject) {
+        if (subject == null) {
+            throw new IllegalArgumentException("No ObjectClass could be provided for " + subject);
+        }
+
+        ObjectClass result = null;
+        if (subject instanceof User) {
+            result = ObjectClass.ACCOUNT;
+        }
+        if (subject instanceof Role) {
+            result = ObjectClass.GROUP;
+        }
+
+        return result;
+    }
+
+    /**
+     * Build a UserTO / RoleTO out of connector object attributes and schema mapping.
+     *
+     * @param obj connector object
+     * @param syncTask synchronization task
+     * @param attrUtil AttributableUtil
+     * @param <T> user/role
+     * @return UserTO for the user to be created
+     */
+    @Transactional(readOnly = true)
+    public <T extends AbstractSubjectTO> T getSubjectTO(final ConnectorObject obj, final SyncTask syncTask,
+            final AttributableUtil attrUtil) {
+
+        T subjectTO = getSubjectTOFromConnObject(obj, syncTask, attrUtil);
+
+        // (for users) if password was not set above, generate
+        if (subjectTO instanceof UserTO && StringUtils.isBlank(((UserTO) subjectTO).getPassword())) {
+            final UserTO userTO = (UserTO) subjectTO;
+
+            List<PasswordPolicySpec> ppSpecs = new ArrayList<>();
+
+            PasswordPolicy globalPP = policyDAO.getGlobalPasswordPolicy();
+            if (globalPP != null && globalPP.getSpecification(PasswordPolicySpec.class) != null) {
+                ppSpecs.add(globalPP.getSpecification(PasswordPolicySpec.class));
+            }
+
+            for (MembershipTO memb : userTO.getMemberships()) {
+                Role role = roleDAO.find(memb.getRoleId());
+                if (role != null && role.getPasswordPolicy() != null
+                        && role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) {
+
+                    ppSpecs.add(role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class));
+                }
+            }
+
+            for (String resName : userTO.getResources()) {
+                ExternalResource resource = resourceDAO.find(resName);
+                if (resource != null && resource.getPasswordPolicy() != null
+                        && resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) {
+
+                    ppSpecs.add(resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class));
+                }
+            }
+
+            String password;
+            try {
+                password = pwdGen.generate(ppSpecs);
+            } catch (InvalidPasswordPolicySpecException e) {
+                LOG.error("Could not generate policy-compliant random password for {}", userTO, e);
+
+                password = SecureRandomUtil.generateRandomPassword(16);
+            }
+            userTO.setPassword(password);
+        }
+
+        return subjectTO;
+    }
+
+    /**
+     * Build an UserMod out of connector object attributes and schema mapping.
+     *
+     * @param key user to be updated
+     * @param obj connector object
+     * @param original subject to get diff from
+     * @param syncTask synchronization task
+     * @param attrUtil AttributableUtil
+     * @param <T> user/role
+     * @return modifications for the user/role to be updated
+     * @throws NotFoundException if given id does not correspond to a T instance
+     * @throws UnauthorizedRoleException if there are no enough entitlements to access the T instance
+     */
+    @SuppressWarnings("unchecked")
+    @Transactional(readOnly = true)
+    public <T extends AbstractAttributableMod> T getAttributableMod(final Long key, final ConnectorObject obj,
+            final AbstractAttributableTO original, final SyncTask syncTask, final AttributableUtil attrUtil)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        final AbstractAttributableTO updated = getSubjectTOFromConnObject(obj, syncTask, attrUtil);
+        updated.setKey(key);
+
+        if (AttributableType.USER == attrUtil.getType()) {
+            // update password if and only if password is really changed
+            final User user = userDAO.authFecthUser(key);
+            if (StringUtils.isBlank(((UserTO) updated).getPassword())
+                    || encryptor.verify(((UserTO) updated).getPassword(),
+                            user.getCipherAlgorithm(), user.getPassword())) {
+
+                ((UserTO) updated).setPassword(null);
+            }
+
+            for (MembershipTO membTO : ((UserTO) updated).getMemberships()) {
+                Membership memb = user.getMembership(membTO.getRoleId());
+                if (memb != null) {
+                    membTO.setKey(memb.getKey());
+                }
+            }
+
+            return (T) AttributableOperations.diff(((UserTO) updated), ((UserTO) original), true);
+        }
+        if (AttributableType.ROLE == attrUtil.getType()) {
+            // reading from connector object cannot change entitlements
+            ((RoleTO) updated).getEntitlements().addAll(((RoleTO) original).getEntitlements());
+            return (T) AttributableOperations.diff(((RoleTO) updated), ((RoleTO) original), true);
+        }
+
+        return null;
+    }
+
+    private <T extends AbstractSubjectTO> T getSubjectTOFromConnObject(final ConnectorObject obj,
+            final SyncTask syncTask, final AttributableUtil attrUtil) {
+
+        final T subjectTO = attrUtil.newSubjectTO();
+
+        // 1. fill with data from connector object
+        for (MappingItem item : attrUtil.getUidToMappingItems(
+                syncTask.getResource(), MappingPurpose.SYNCHRONIZATION)) {
+
+            Attribute attribute = obj.getAttributeByName(item.getExtAttrName());
+
+            AttrTO attributeTO;
+            switch (item.getIntMappingType()) {
+                case UserId:
+                case RoleId:
+                    break;
+
+                case Password:
+                    if (subjectTO instanceof UserTO && attribute != null && attribute.getValue() != null
+                            && !attribute.getValue().isEmpty()) {
+
+                        ((UserTO) subjectTO).setPassword(getPassword(attribute.getValue().get(0)));
+                    }
+                    break;
+
+                case Username:
+                    if (subjectTO instanceof UserTO) {
+                        ((UserTO) subjectTO).setUsername(attribute == null || attribute.getValue().isEmpty()
+                                || attribute.getValue().get(0) == null
+                                        ? null
+                                        : attribute.getValue().get(0).toString());
+                    }
+                    break;
+
+                case RoleName:
+                    if (subjectTO instanceof RoleTO) {
+                        ((RoleTO) subjectTO).setName(attribute == null || attribute.getValue().isEmpty()
+                                || attribute.getValue().get(0) == null
+                                        ? null
+                                        : attribute.getValue().get(0).toString());
+                    }
+                    break;
+
+                case RoleOwnerSchema:
+                    if (subjectTO instanceof RoleTO && attribute != null) {
+                        // using a special attribute (with schema "", that will be ignored) for carrying the
+                        // RoleOwnerSchema value
+                        attributeTO = new AttrTO();
+                        attributeTO.setSchema(StringUtils.EMPTY);
+                        if (attribute.getValue().isEmpty() || attribute.getValue().get(0) == null) {
+                            attributeTO.getValues().add(StringUtils.EMPTY);
+                        } else {
+                            attributeTO.getValues().add(attribute.getValue().get(0).toString());
+                        }
+
+                        ((RoleTO) subjectTO).getPlainAttrs().add(attributeTO);
+                    }
+                    break;
+
+                case UserSchema:
+                case RoleSchema:
+                    attributeTO = new AttrTO();
+                    attributeTO.setSchema(item.getIntAttrName());
+
+                    PlainSchema schema = plainSchemaDAO.find(item.getIntAttrName(), attrUtil.plainSchemaClass());
+
+                    for (Object value : attribute == null || attribute.getValue() == null
+                            ? Collections.emptyList()
+                            : attribute.getValue()) {
+
+                        AttrSchemaType schemaType = schema == null ? AttrSchemaType.String : schema.getType();
+                        if (value != null) {
+                            final PlainAttrValue attrValue = attrUtil.newPlainAttrValue();
+                            switch (schemaType) {
+                                case String:
+                                    attrValue.setStringValue(value.toString());
+                                    break;
+
+                                case Binary:
+                                    attrValue.setBinaryValue((byte[]) value);
+                                    break;
+
+                                default:
+                                    try {
+                                        attrValue.parseValue(schema, value.toString());
+                                    } catch (ParsingValidationException e) {
+                                        LOG.error("While parsing provided value {}", value, e);
+                                        attrValue.setStringValue(value.toString());
+                                        schemaType = AttrSchemaType.String;
+                                    }
+                            }
+                            attributeTO.getValues().add(attrValue.getValueAsString(schemaType));
+                        }
+                    }
+
+                    subjectTO.getPlainAttrs().add(attributeTO);
+                    break;
+
+                case UserDerivedSchema:
+                case RoleDerivedSchema:
+                    attributeTO = new AttrTO();
+                    attributeTO.setSchema(item.getIntAttrName());
+                    subjectTO.getDerAttrs().add(attributeTO);
+                    break;
+
+                case UserVirtualSchema:
+                case RoleVirtualSchema:
+                    attributeTO = new AttrTO();
+                    attributeTO.setSchema(item.getIntAttrName());
+
+                    for (Object value : attribute == null || attribute.getValue() == null
+                            ? Collections.emptyList()
+                            : attribute.getValue()) {
+
+                        if (value != null) {
+                            attributeTO.getValues().add(value.toString());
+                        }
+                    }
+
+                    subjectTO.getVirAttrs().add(attributeTO);
+                    break;
+
+                default:
+            }
+        }
+
+        // 2. add data from defined template (if any)
+        AbstractSubjectTO template = AttributableType.USER == attrUtil.getType()
+                ? syncTask.getUserTemplate() : syncTask.getRoleTemplate();
+
+        if (template != null) {
+            if (template instanceof UserTO) {
+                if (StringUtils.isNotBlank(((UserTO) template).getUsername())) {
+                    String evaluated = JexlUtil.evaluate(((UserTO) template).getUsername(), subjectTO);
+                    if (StringUtils.isNotBlank(evaluated)) {
+                        ((UserTO) subjectTO).setUsername(evaluated);
+                    }
+                }
+
+                if (StringUtils.isNotBlank(((UserTO) template).getPassword())) {
+                    String evaluated = JexlUtil.evaluate(((UserTO) template).getPassword(), subjectTO);
+                    if (StringUtils.isNotBlank(evaluated)) {
+                        ((UserTO) subjectTO).setPassword(evaluated);
+                    }
+                }
+
+                Map<Long, MembershipTO> currentMembs = ((UserTO) subjectTO).getMembershipMap();
+                for (MembershipTO membTO : ((UserTO) template).getMemberships()) {
+                    MembershipTO membTBU;
+                    if (currentMembs.containsKey(membTO.getRoleId())) {
+                        membTBU = currentMembs.get(membTO.getRoleId());
+                    } else {
+                        membTBU = new MembershipTO();
+                        membTBU.setRoleId(membTO.getRoleId());
+                        ((UserTO) subjectTO).getMemberships().add(membTBU);
+                    }
+                    fillFromTemplate(membTBU, membTO);
+                }
+            }
+            if (template instanceof RoleTO) {
+                if (StringUtils.isNotBlank(((RoleTO) template).getName())) {
+                    String evaluated = JexlUtil.evaluate(((RoleTO) template).getName(), subjectTO);
+                    if (StringUtils.isNotBlank(evaluated)) {
+                        ((RoleTO) subjectTO).setName(evaluated);
+                    }
+                }
+
+                if (((RoleTO) template).getParent() != 0) {
+                    final Role parentRole = roleDAO.find(((RoleTO) template).getParent());
+                    if (parentRole != null) {
+                        ((RoleTO) subjectTO).setParent(parentRole.getKey());
+                    }
+                }
+
+                if (((RoleTO) template).getUserOwner() != null) {
+                    final User userOwner = userDAO.find(((RoleTO) template).getUserOwner());
+                    if (userOwner != null) {
+                        ((RoleTO) subjectTO).setUserOwner(userOwner.getKey());
+                    }
+                }
+                if (((RoleTO) template).getRoleOwner() != null) {
+                    final Role roleOwner = roleDAO.find(((RoleTO) template).getRoleOwner());
+                    if (roleOwner != null) {
+                        ((RoleTO) subjectTO).setRoleOwner(roleOwner.getKey());
+                    }
+                }
+
+                ((RoleTO) subjectTO).getEntitlements().addAll(((RoleTO) template).getEntitlements());
+
+                ((RoleTO) subjectTO).getRAttrTemplates().addAll(((RoleTO) template).getRAttrTemplates());
+                ((RoleTO) subjectTO).getRDerAttrTemplates().addAll(((RoleTO) template).getRDerAttrTemplates());
+                ((RoleTO) subjectTO).getRVirAttrTemplates().addAll(((RoleTO) template).getRVirAttrTemplates());
+                ((RoleTO) subjectTO).getMAttrTemplates().addAll(((RoleTO) template).getMAttrTemplates());
+                ((RoleTO) subjectTO).getMDerAttrTemplates().addAll(((RoleTO) template).getMDerAttrTemplates());
+                ((RoleTO) subjectTO).getMVirAttrTemplates().addAll(((RoleTO) template).getMVirAttrTemplates());
+
+                ((RoleTO) subjectTO).setAccountPolicy(((RoleTO) template).getAccountPolicy());
+                ((RoleTO) subjectTO).setPasswordPolicy(((RoleTO) template).getPasswordPolicy());
+
+                ((RoleTO) subjectTO).setInheritOwner(((RoleTO) template).isInheritOwner());
+                ((RoleTO) subjectTO).setInheritTemplates(((RoleTO) template).isInheritTemplates());
+                ((RoleTO) subjectTO).setInheritAttrs(((RoleTO) template).isInheritAttrs());
+                ((RoleTO) subjectTO).setInheritDerAttrs(((RoleTO) template).isInheritDerAttrs());
+                ((RoleTO) subjectTO).setInheritVirAttrs(((RoleTO) template).isInheritVirAttrs());
+                ((RoleTO) subjectTO).setInheritPasswordPolicy(((RoleTO) template).isInheritPasswordPolicy());
+                ((RoleTO) subjectTO).setInheritAccountPolicy(((RoleTO) template).isInheritAccountPolicy());
+            }
+
+            fillFromTemplate(subjectTO, template);
+
+            for (String resource : template.getResources()) {
+                subjectTO.getResources().add(resource);
+            }
+        }
+
+        return subjectTO;
+    }
+
+    /**
+     * Extract password value from passed value (if instance of GuardedString or GuardedByteArray).
+     *
+     * @param pwd received from the underlying connector
+     * @return password value
+     */
+    public String getPassword(final Object pwd) {
+        final StringBuilder result = new StringBuilder();
+
+        if (pwd instanceof GuardedString) {
+            ((GuardedString) pwd).access(new GuardedString.Accessor() {
+
+                @Override
+                public void access(final char[] clearChars) {
+                    result.append(clearChars);
+                }
+            });
+        } else if (pwd instanceof GuardedByteArray) {
+            ((GuardedByteArray) pwd).access(new GuardedByteArray.Accessor() {
+
+                @Override
+                public void access(final byte[] clearBytes) {
+                    result.append(new String(clearBytes));
+                }
+            });
+        } else if (pwd instanceof String) {
+            result.append((String) pwd);
+        } else {
+            result.append(pwd.toString());
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Get connector object TO from a connector object.
+     *
+     * @param connObject connector object.
+     * @return connector object TO.
+     */
+    public ConnObjectTO getConnObjectTO(final ConnectorObject connObject) {
+        final ConnObjectTO connObjectTO = new ConnObjectTO();
+
+        for (Attribute attr : connObject.getAttributes()) {
+            AttrTO attrTO = new AttrTO();
+            attrTO.setSchema(attr.getName());
+
+            if (attr.getValue() != null) {
+                for (Object value : attr.getValue()) {
+                    if (value != null) {
+                        if (value instanceof GuardedString || value instanceof GuardedByteArray) {
+                            attrTO.getValues().add(getPassword(value));
+                        } else if (value instanceof byte[]) {
+                            attrTO.getValues().add(Base64.encode((byte[]) value));
+                        } else {
+                            attrTO.getValues().add(value.toString());
+                        }
+                    }
+                }
+            }
+
+            connObjectTO.getPlainAttrs().add(attrTO);
+        }
+
+        return connObjectTO;
+    }
+
+    /**
+     * Query connected external resources for values to populated virtual attributes associated with the given owner.
+     *
+     * @param owner user or role
+     * @param attrUtil attributable util
+     */
+    public void retrieveVirAttrValues(final Attributable<?, ?, ?> owner, final AttributableUtil attrUtil) {
+        final ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext();
+        final ConnectorFactory connFactory = context.getBean(ConnectorFactory.class);
+
+        final IntMappingType type = attrUtil.getType() == AttributableType.USER
+                ? IntMappingType.UserVirtualSchema : attrUtil.getType() == AttributableType.ROLE
+                        ? IntMappingType.RoleVirtualSchema : IntMappingType.MembershipVirtualSchema;
+
+        final Map<String, ConnectorObject> externalResources = new HashMap<>();
+
+        // -----------------------
+        // Retrieve virtual attribute values if and only if they have not been retrieved yet
+        // -----------------------
+        for (VirAttr virAttr : owner.getVirAttrs()) {
+            // reset value set
+            if (virAttr.getValues().isEmpty()) {
+                retrieveVirAttrValue(owner, virAttr, attrUtil, type, externalResources, connFactory);
+            }
+        }
+        // -----------------------
+    }
+
+    private void retrieveVirAttrValue(
+            final Attributable<?, ?, ?> owner,
+            final VirAttr virAttr,
+            final AttributableUtil attrUtil,
+            final IntMappingType type,
+            final Map<String, ConnectorObject> externalResources,
+            final ConnectorFactory connFactory) {
+
+        final String schemaName = virAttr.getSchema().getKey();
+        final VirAttrCacheValue virAttrCacheValue = virAttrCache.get(attrUtil.getType(), owner.getKey(), schemaName);
+
+        LOG.debug("Retrieve values for virtual attribute {} ({})", schemaName, type);
+
+        if (virAttrCache.isValidEntry(virAttrCacheValue)) {
+            // cached ...
+            LOG.debug("Values found in cache {}", virAttrCacheValue);
+            virAttr.getValues().clear();
+            virAttr.getValues().addAll(new ArrayList<>(virAttrCacheValue.getValues()));
+        } else {
+            // not cached ...
+            LOG.debug("Need one or more remote connections");
+
+            final VirAttrCacheValue toBeCached = new VirAttrCacheValue();
+
+            // SYNCOPE-458 if virattr owner is a Membership, owner must become user involved in membership because 
+            // membership mapping is contained in user mapping
+            final Subject<?, ?, ?> realOwner = owner instanceof Membership
+                    ? ((Membership) owner).getUser()
+                    : (Subject) owner;
+
+            final Set<ExternalResource> targetResources = owner instanceof Membership
+                    ? getTargetResource(virAttr, type, attrUtil, realOwner.getResources())
+                    : getTargetResource(virAttr, type, attrUtil);
+
+            for (ExternalResource resource : targetResources) {
+                LOG.debug("Search values into {}", resource.getKey());
+                try {
+                    final List<MappingItem> mappings = attrUtil.getMappingItems(resource, MappingPurpose.BOTH);
+
+                    final ConnectorObject connectorObject;
+
+                    if (externalResources.containsKey(resource.getKey())) {
+                        connectorObject = externalResources.get(resource.getKey());
+                    } else {
+                        LOG.debug("Perform connection to {}", resource.getKey());
+                        final String accountId = attrUtil.getAccountIdItem(resource) == null
+                                ? null
+                                : MappingUtil.getAccountIdValue(
+                                        realOwner, resource, attrUtil.getAccountIdItem(resource));
+
+                        if (StringUtils.isBlank(accountId)) {
+                            throw new IllegalArgumentException("No AccountId found for " + resource.getKey());
+                        }
+
+                        final Connector connector = connFactory.getConnector(resource);
+
+                        final OperationOptions oo =
+                                connector.getOperationOptions(MappingUtil.getMatchingMappingItems(mappings, type));
+
+                        connectorObject = connector.getObject(fromSubject(realOwner), new Uid(accountId), oo);
+                        externalResources.put(resource.getKey(), connectorObject);
+                    }
+
+                    if (connectorObject != null) {
+                        // ask for searched virtual attribute value
+                        final List<MappingItem> virAttrMappings =
+                                MappingUtil.getMatchingMappingItems(mappings, schemaName, type);
+
+                        // the same virtual attribute could be mapped with one or more external attribute 
+                        for (MappingItem mapping : virAttrMappings) {
+                            final Attribute attribute = connectorObject.getAttributeByName(mapping.getExtAttrName());
+
+                            if (attribute != null && attribute.getValue() != null) {
+                                for (Object obj : attribute.getValue()) {
+                                    if (obj != null) {
+                                        virAttr.getValues().add(obj.toString());
+                                    }
+                                }
+                            }
+                        }
+
+                        toBeCached.setResourceValues(resource.getKey(), new HashSet<String>(virAttr.getValues()));
+
+                        LOG.debug("Retrieved values {}", virAttr.getValues());
+                    }
+                } catch (Exception e) {
+                    LOG.error("Error reading connector object from {}", resource.getKey(), e);
+
+                    if (virAttrCacheValue != null) {
+                        toBeCached.forceExpiring();
+                        LOG.debug("Search for a cached value (even expired!) ...");
+                        final Set<String> cachedValues = virAttrCacheValue.getValues(resource.getKey());
+                        if (cachedValues != null) {
+                            LOG.debug("Use cached value {}", cachedValues);
+                            virAttr.getValues().addAll(cachedValues);
+                            toBeCached.setResourceValues(resource.getKey(), new HashSet<>(cachedValues));
+                        }
+                    }
+                }
+            }
+
+            virAttrCache.put(attrUtil.getType(), owner.getKey(), schemaName, toBeCached);
+        }
+    }
+
+    private Set<ExternalResource> getTargetResource(
+            final VirAttr attr, final IntMappingType type, final AttributableUtil attrUtil) {
+
+        final Set<ExternalResource> resources = new HashSet<>();
+
+        if (attr.getOwner() instanceof Subject) {
+            for (ExternalResource res : ((Subject<?, ?, ?>) attr.getOwner()).getResources()) {
+                if (!MappingUtil.getMatchingMappingItems(
+                        attrUtil.getMappingItems(res, MappingPurpose.BOTH),
+                        attr.getSchema().getKey(), type).isEmpty()) {
+
+                    resources.add(res);
+                }
+            }
+        }
+
+        return resources;
+    }
+
+    private Set<ExternalResource> getTargetResource(final VirAttr attr, final IntMappingType type,
+            final AttributableUtil attrUtil, final Set<? extends ExternalResource> ownerResources) {
+
+        final Set<ExternalResource> resources = new HashSet<>();
+
+        for (ExternalResource res : ownerResources) {
+            if (!MappingUtil.getMatchingMappingItems(
+                    attrUtil.getMappingItems(res, MappingPurpose.BOTH),
+                    attr.getSchema().getKey(), type).isEmpty()) {
+
+                resources.add(res);
+            }
+        }
+
+        return resources;
+    }
+
+    private void fillFromTemplate(final AbstractAttributableTO attributableTO, final AbstractAttributableTO template) {
+        Map<String, AttrTO> currentAttrMap = attributableTO.getAttrMap();
+        for (AttrTO templateAttr : template.getPlainAttrs()) {
+            if (templateAttr.getValues() != null && !templateAttr.getValues().isEmpty()
+                    && (!currentAttrMap.containsKey(templateAttr.getSchema())
+                    || currentAttrMap.get(templateAttr.getSchema()).getValues().isEmpty())) {
+
+                attributableTO.getPlainAttrs().add(evaluateAttrTemplate(attributableTO, templateAttr));
+            }
+        }
+
+        currentAttrMap = attributableTO.getDerAttrMap();
+        for (AttrTO templateDerAttr : template.getDerAttrs()) {
+            if (!currentAttrMap.containsKey(templateDerAttr.getSchema())) {
+                attributableTO.getDerAttrs().add(templateDerAttr);
+            }
+        }
+
+        currentAttrMap = attributableTO.getVirAttrMap();
+        for (AttrTO templateVirAttr : template.getVirAttrs()) {
+            if (templateVirAttr.getValues() != null && !templateVirAttr.getValues().isEmpty()
+                    && (!currentAttrMap.containsKey(templateVirAttr.getSchema())
+                    || currentAttrMap.get(templateVirAttr.getSchema()).getValues().isEmpty())) {
+
+                attributableTO.getVirAttrs().add(evaluateAttrTemplate(attributableTO, templateVirAttr));
+            }
+        }
+    }
+
+    private AttrTO evaluateAttrTemplate(final AbstractAttributableTO attributableTO, final AttrTO template) {
+        AttrTO result = new AttrTO();
+        result.setSchema(template.getSchema());
+
+        if (template.getValues() != null && !template.getValues().isEmpty()) {
+            for (String value : template.getValues()) {
+                String evaluated = JexlUtil.evaluate(value, attributableTO);
+                if (StringUtils.isNotBlank(evaluated)) {
+                    result.getValues().add(evaluated);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Transform a
+     * <code>Collection</code> of {@link Attribute} instances into a {@link Map}. The key to each element in the map is
+     * the <i>name</i> of an
+     * <code>Attribute</code>. The value of each element in the map is the
+     * <code>Attribute</code> instance with that name. <br/> Different from the original because: <ul> <li>map keys are
+     * transformed toUpperCase()</li> <li>returned map is mutable</li> </ul>
+     *
+     * @param attributes set of attribute to transform to a map.
+     * @return a map of string and attribute.
+     *
+     * @see org.identityconnectors.framework.common.objects.AttributeUtil#toMap(java.util.Collection)
+     */
+    public Map<String, Attribute> toMap(final Collection<? extends Attribute> attributes) {
+        final Map<String, Attribute> map = new HashMap<>();
+        for (Attribute attr : attributes) {
+            map.put(attr.getName().toUpperCase(), attr);
+        }
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ExceptionUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ExceptionUtil.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ExceptionUtil.java
new file mode 100644
index 0000000..5a39faf
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ExceptionUtil.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.utils;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+public final class ExceptionUtil {
+
+    /**
+     * Uses commons lang's ExceptionUtils to provide a representation of the full stack trace of the given throwable.
+     *
+     * @param t throwable to build stack trace from
+     * @return a string representation of full stack trace of the given throwable
+     */
+    public static String getFullStackTrace(final Throwable t) {
+        StringBuilder result = new StringBuilder();
+
+        for (Throwable throwable : ExceptionUtils.getThrowableList(t)) {
+            result.append(ExceptionUtils.getMessage(throwable)).append('\n').
+                    append(ExceptionUtils.getStackTrace(throwable)).append("\n\n");
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Private default constructor, for static-only classes.
+     */
+    private ExceptionUtil() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/InvalidPasswordPolicySpecException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/InvalidPasswordPolicySpecException.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/InvalidPasswordPolicySpecException.java
new file mode 100644
index 0000000..8c97a1c
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/InvalidPasswordPolicySpecException.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.utils;
+
+/**
+ * Raise when the merge of two or more PasswordPolicySpec leds to incompatible condition.
+ *
+ * @see org.apache.syncope.common.lib.types.PasswordPolicySpec
+ */
+public class InvalidPasswordPolicySpecException extends Exception {
+
+    private static final long serialVersionUID = 4810651743226663580L;
+
+    public InvalidPasswordPolicySpecException(final String msg) {
+        super(msg);
+    }
+
+    public InvalidPasswordPolicySpecException(final String msg, final Exception e) {
+        super(msg, e);
+    }
+}