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/01 19:12:10 UTC

[15/32] syncope git commit: [SYNCOPE-620] JPA entities + basic tests

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/BeanUtils.java
----------------------------------------------------------------------
diff --git a/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/BeanUtils.java b/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/BeanUtils.java
new file mode 100644
index 0000000..f937b40
--- /dev/null
+++ b/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/BeanUtils.java
@@ -0,0 +1,201 @@
+/*
+ * 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.spring;
+
+import static org.springframework.beans.BeanUtils.getPropertyDescriptor;
+import static org.springframework.beans.BeanUtils.getPropertyDescriptors;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.FatalBeanException;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Overrides Spring's BeanUtils not using collection setters but instead getters + addAll() / putAll(),
+ * in a JAXB friendly way.
+ *
+ * Refer to <a href="https://issues.apache.org/jira/browse/SYNCOPE-246">SYNCOPE-246</a> for more information.
+ *
+ * @see org.springframework.beans.BeanUtils
+ */
+public final class BeanUtils {
+
+    private BeanUtils() {
+        // Empty private constructor for static utility classes
+    }
+
+    /**
+     * Copy the property values of the given source bean into the target bean.
+     * <p>
+     * Note: The source and target classes do not have to match or even be derived
+     * from each other, as long as the properties match. Any bean properties that the
+     * source bean exposes but the target bean does not will silently be ignored.
+     * </p><p>
+     * This is just a convenience method. For more complex transfer needs,
+     * consider using a full BeanWrapper.
+     * </p>
+     * @param source the source bean
+     * @param target the target bean
+     * @throws BeansException if the copying failed
+     * @see org.springframework.beans.BeanWrapper
+     */
+    public static void copyProperties(final Object source, final Object target) throws BeansException {
+        copyProperties(source, target, null, (String[]) null);
+    }
+
+    /**
+     * Copy the property values of the given source bean into the given target bean,
+     * only setting properties defined in the given "editable" class (or interface).
+     * <p>
+     * Note: The source and target classes do not have to match or even be derived
+     * from each other, as long as the properties match. Any bean properties that the
+     * source bean exposes but the target bean does not will silently be ignored.
+     * </p><p>
+     * This is just a convenience method. For more complex transfer needs,
+     * consider using a full BeanWrapper.
+     * </p>
+     *
+     * @param source the source bean
+     * @param target the target bean
+     * @param editable the class (or interface) to restrict property setting to
+     * @throws BeansException if the copying failed
+     * @see org.springframework.beans.BeanWrapper
+     */
+    public static void copyProperties(final Object source, final Object target, final Class<?> editable)
+            throws BeansException {
+
+        copyProperties(source, target, editable, (String[]) null);
+    }
+
+    /**
+     * Copy the property values of the given source bean into the given target bean,
+     * ignoring the given "ignoreProperties".
+     * <p>
+     * Note: The source and target classes do not have to match or even be derived
+     * from each other, as long as the properties match. Any bean properties that the
+     * source bean exposes but the target bean does not will silently be ignored.
+     * </p><p>
+     * This is just a convenience method. For more complex transfer needs,
+     * consider using a full BeanWrapper.
+     * </p>
+     *
+     * @param source the source bean
+     * @param target the target bean
+     * @param ignoreProperties array of property names to ignore
+     * @throws BeansException if the copying failed
+     * @see org.springframework.beans.BeanWrapper
+     */
+    public static void copyProperties(final Object source, final Object target, final String... ignoreProperties)
+            throws BeansException {
+
+        copyProperties(source, target, null, ignoreProperties);
+    }
+
+    /**
+     * Copy the property values of the given source bean into the given target bean.
+     * <p>
+     * Note: The source and target classes do not have to match or even be derived
+     * from each other, as long as the properties match. Any bean properties that the
+     * source bean exposes but the target bean does not will silently be ignored.
+     * </p>
+     *
+     * @param source the source bean
+     * @param target the target bean
+     * @param editable the class (or interface) to restrict property setting to
+     * @param ignoreProperties array of property names to ignore
+     * @throws BeansException if the copying failed
+     * @see org.springframework.beans.BeanWrapper
+     */
+    @SuppressWarnings("unchecked")
+    private static void copyProperties(final Object source, final Object target, final Class<?> editable,
+            final String... ignoreProperties) throws BeansException {
+
+        Assert.notNull(source, "Source must not be null");
+        Assert.notNull(target, "Target must not be null");
+
+        Class<?> actualEditable = target.getClass();
+        if (editable != null) {
+            if (!editable.isInstance(target)) {
+                throw new IllegalArgumentException("Target class [" + target.getClass().getName()
+                        + "] not assignable to Editable class [" + editable.getName() + "]");
+            }
+            actualEditable = editable;
+        }
+        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
+        List<String> ignoreList = (ignoreProperties == null)
+                ? Collections.<String>emptyList() : Arrays.asList(ignoreProperties);
+
+        for (PropertyDescriptor targetPd : targetPds) {
+            if (ignoreProperties == null || (!ignoreList.contains(targetPd.getName()))) {
+                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
+                if (sourcePd != null) {
+                    Method readMethod = sourcePd.getReadMethod();
+                    if (readMethod != null) {
+                        Method writeMethod = targetPd.getWriteMethod();
+
+                        try {
+                            // Diverts from Spring's BeanUtils: if no write method is found and property is collection,
+                            // try to use addAll() / putAll().
+                            if (writeMethod == null) {
+                                Object value = readMethod.invoke(source);
+                                Method targetReadMethod = targetPd.getReadMethod();
+                                if (targetReadMethod != null) {
+                                    if (!Modifier.isPublic(targetReadMethod.getDeclaringClass().getModifiers())) {
+                                        targetReadMethod.setAccessible(true);
+                                    }
+                                    Object destValue = targetReadMethod.invoke(target);
+
+                                    if (value instanceof Collection && destValue instanceof Collection) {
+                                        ((Collection) destValue).clear();
+                                        ((Collection) destValue).addAll((Collection) value);
+                                    } else if (value instanceof Map && destValue instanceof Map) {
+                                        ((Map) destValue).clear();
+                                        ((Map) destValue).putAll((Map) value);
+                                    }
+                                }
+                            } else if (ClassUtils.isAssignable(
+                                    writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
+
+                                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
+                                    readMethod.setAccessible(true);
+                                }
+                                Object value = readMethod.invoke(source);
+                                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
+                                    writeMethod.setAccessible(true);
+                                }
+                                writeMethod.invoke(target, value);
+                            }
+                        } catch (Throwable ex) {
+                            throw new FatalBeanException(
+                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/ResourceWithFallbackLoader.java
----------------------------------------------------------------------
diff --git a/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/ResourceWithFallbackLoader.java b/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/ResourceWithFallbackLoader.java
new file mode 100644
index 0000000..9b771b3
--- /dev/null
+++ b/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/ResourceWithFallbackLoader.java
@@ -0,0 +1,82 @@
+/*
+ * 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.spring;
+
+import java.io.IOException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.ResourcePatternResolver;
+
+public class ResourceWithFallbackLoader implements ResourceLoaderAware, ResourcePatternResolver {
+
+    private ResourcePatternResolver resolver;
+
+    private String primary;
+
+    private String fallback;
+
+    @Override
+    public void setResourceLoader(final ResourceLoader resourceLoader) {
+        this.resolver = (ResourcePatternResolver) resourceLoader;
+    }
+
+    public void setPrimary(final String primary) {
+        this.primary = primary;
+    }
+
+    public void setFallback(final String fallback) {
+        this.fallback = fallback;
+    }
+
+    @Override
+    public Resource getResource(final String location) {
+        Resource resource = resolver.getResource(primary + location);
+        if (!resource.exists()) {
+            resource = resolver.getResource(fallback + location);
+        }
+
+        return resource;
+    }
+
+    public Resource getResource() {
+        return getResource(StringUtils.EMPTY);
+    }
+
+    @Override
+    public Resource[] getResources(final String locationPattern) throws IOException {
+        Resource[] resources = resolver.getResources(primary + locationPattern);
+        if (ArrayUtils.isEmpty(resources)) {
+            resources = resolver.getResources(fallback + locationPattern);
+        }
+
+        return resources;
+    }
+
+    public Resource[] getResources() throws IOException {
+        return getResources(StringUtils.EMPTY);
+    }
+
+    @Override
+    public ClassLoader getClassLoader() {
+        return resolver.getClassLoader();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/pom.xml b/syncope620/server/utils/pom.xml
new file mode 100644
index 0000000..af4a55e
--- /dev/null
+++ b/syncope620/server/utils/pom.xml
@@ -0,0 +1,75 @@
+<?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 Utilities</name>
+  <description>Apache Syncope Server Utilities</description>
+  <groupId>org.apache.syncope.server</groupId>
+  <artifactId>syncope-server-utils</artifactId>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-jexl</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.module</groupId>
+      <artifactId>jackson-module-afterburner</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-context</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-persistence-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/DataFormat.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/DataFormat.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/DataFormat.java
new file mode 100644
index 0000000..d30f3c4
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/DataFormat.java
@@ -0,0 +1,117 @@
+/*
+ * 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.text.DecimalFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import org.apache.commons.lang3.time.DateUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+
+/**
+ * Utility class for parsing / formatting date and numbers.
+ */
+public final class DataFormat {
+
+    private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
+
+        @Override
+        protected SimpleDateFormat initialValue() {
+            SimpleDateFormat sdf = new SimpleDateFormat();
+            sdf.applyPattern(SyncopeConstants.DEFAULT_DATE_PATTERN);
+            return sdf;
+        }
+    };
+
+    private static final ThreadLocal<DecimalFormat> DECIMAL_FORMAT = new ThreadLocal<DecimalFormat>() {
+
+        @Override
+        protected DecimalFormat initialValue() {
+            return new DecimalFormat();
+        }
+    };
+
+    public static String format(final Date date) {
+        return format(date, true);
+    }
+
+    public static String format(final Date date, final boolean lenient) {
+        return format(date, lenient, null);
+    }
+
+    public static String format(final Date date, final boolean lenient, final String conversionPattern) {
+        SimpleDateFormat sdf = DATE_FORMAT.get();
+        if (conversionPattern != null) {
+            sdf.applyPattern(conversionPattern);
+        }
+        sdf.setLenient(lenient);
+        return sdf.format(date);
+    }
+
+    public static String format(final long number) {
+        return format(number, null);
+    }
+
+    public static String format(final long number, final String conversionPattern) {
+        DecimalFormat df = DECIMAL_FORMAT.get();
+        if (conversionPattern != null) {
+            df.applyPattern(conversionPattern);
+        }
+        return df.format(number);
+    }
+
+    public static String format(final double number) {
+        return format(number, null);
+    }
+
+    public static String format(final double number, final String conversionPattern) {
+        DecimalFormat df = DECIMAL_FORMAT.get();
+        if (conversionPattern != null) {
+            df.applyPattern(conversionPattern);
+        }
+        return df.format(number);
+    }
+
+    public static Date parseDate(final String source) throws ParseException {
+        return DateUtils.parseDate(source, SyncopeConstants.DATE_PATTERNS);
+    }
+
+    public static Date parseDate(final String source, final String conversionPattern) throws ParseException {
+        SimpleDateFormat sdf = DATE_FORMAT.get();
+        sdf.applyPattern(conversionPattern);
+        sdf.setLenient(false);
+        return sdf.parse(source);
+    }
+
+    public static Number parseNumber(final String source, final String conversionPattern) throws ParseException {
+        DecimalFormat df = DECIMAL_FORMAT.get();
+        df.applyPattern(conversionPattern);
+        return df.parse(source);
+    }
+
+    public static void clear() {
+        DATE_FORMAT.remove();
+        DECIMAL_FORMAT.remove();
+    }
+
+    private DataFormat() {
+        // private empty constructor
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java
new file mode 100644
index 0000000..903b8ca
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java
@@ -0,0 +1,61 @@
+/*
+ * 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.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+public final class URIUtil {
+
+    private URIUtil() {
+        // empty constructor for static utility class
+    }
+
+    /**
+     * Build a valid URI out of the given location.
+     * Only "file", "connid" and "connids" schemes are allowed.
+     * For "file", invalid characters are handled via intermediate transformation into URL.
+     *
+     * @param location the candidate location for URI
+     * @return valid URI for the given location
+     * @throws MalformedURLException if the intermediate URL is not valid
+     * @throws URISyntaxException if the given location does not correspond to a valid URI
+     */
+    public static URI buildForConnId(final String location) throws MalformedURLException, URISyntaxException {
+        final String candidate = location.trim();
+
+        if (!candidate.startsWith("file:")
+                && !candidate.startsWith("connid:") && !candidate.startsWith("connids:")) {
+
+            throw new IllegalArgumentException(candidate + " is not a valid URI for file or connid(s) schemes");
+        }
+
+        URI uri;
+        if (candidate.startsWith("file:")) {
+            uri = new File(new URL(candidate).getFile()).getAbsoluteFile().toURI();
+        } else {
+            uri = new URI(candidate);
+        }
+
+        return uri;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/ClassFreeUberspectImpl.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/ClassFreeUberspectImpl.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/ClassFreeUberspectImpl.java
new file mode 100644
index 0000000..15d06dd
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/ClassFreeUberspectImpl.java
@@ -0,0 +1,43 @@
+/*
+ * 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.jexl;
+
+import org.apache.commons.jexl2.JexlInfo;
+import org.apache.commons.jexl2.introspection.JexlMethod;
+import org.apache.commons.jexl2.introspection.JexlPropertyGet;
+import org.apache.commons.jexl2.introspection.UberspectImpl;
+import org.apache.commons.logging.Log;
+
+class ClassFreeUberspectImpl extends UberspectImpl {
+
+    public ClassFreeUberspectImpl(final Log runtimeLogger) {
+        super(runtimeLogger);
+    }
+
+    @Override
+    public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier, final JexlInfo info) {
+        return "class".equals(identifier) ? null : super.getPropertyGet(obj, identifier, info);
+    }
+
+    @Override
+    public JexlMethod getMethod(final Object obj, final String method, final Object[] args, final JexlInfo info) {
+        return "getClass".equals(method) ? null : super.getMethod(obj, method, args, info);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/EmptyClassLoader.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/EmptyClassLoader.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/EmptyClassLoader.java
new file mode 100644
index 0000000..4564aad
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/EmptyClassLoader.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.server.utils.jexl;
+
+/**
+ * A class loader that will throw {@link ClassNotFoundException} for every class name.
+ */
+class EmptyClassLoader extends ClassLoader {
+
+    @Override
+    public Class<?> loadClass(final String name) throws ClassNotFoundException {
+        throw new ClassNotFoundException("This classloader won't attemp to load " + name);
+    }
+
+    @Override
+    protected Class<?> loadClass(final String name, boolean resolve) throws ClassNotFoundException {
+        throw new ClassNotFoundException("This classloader won't attemp to load " + name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/JexlUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/JexlUtil.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/JexlUtil.java
new file mode 100644
index 0000000..d160ef4
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/JexlUtil.java
@@ -0,0 +1,289 @@
+/*
+ * 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.jexl;
+
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import org.apache.commons.jexl2.Expression;
+import org.apache.commons.jexl2.JexlContext;
+import org.apache.commons.jexl2.JexlEngine;
+import org.apache.commons.jexl2.JexlException;
+import org.apache.commons.jexl2.MapContext;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.DerAttr;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.utils.DataFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * JEXL <a href="http://commons.apache.org/jexl/reference/index.html">reference</a> is available.
+ */
+public final class JexlUtil {
+
+    /**
+     * Logger.
+     *
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(JexlUtil.class);
+
+    private static final String[] IGNORE_FIELDS = { "password", "clearPassword", "serialVersionUID", "class" };
+
+    private static JexlEngine jexlEngine;
+
+    private static JexlEngine getEngine() {
+        synchronized (LOG) {
+            if (jexlEngine == null) {
+                jexlEngine = new JexlEngine(new ClassFreeUberspectImpl(null), null, null, null);
+                jexlEngine.setClassLoader(new EmptyClassLoader());
+                jexlEngine.setCache(512);
+                jexlEngine.setLenient(true);
+                jexlEngine.setSilent(false);
+            }
+        }
+
+        return jexlEngine;
+    }
+
+    public static boolean isExpressionValid(final String expression) {
+        boolean result;
+        try {
+            getEngine().createExpression(expression);
+            result = true;
+        } catch (JexlException e) {
+            LOG.error("Invalid jexl expression: " + expression, e);
+            result = false;
+        }
+
+        return result;
+    }
+
+    public static String evaluate(final String expression, final JexlContext jexlContext) {
+        String result = StringUtils.EMPTY;
+
+        if (StringUtils.isNotBlank(expression) && jexlContext != null) {
+            try {
+                Expression jexlExpression = getEngine().createExpression(expression);
+                Object evaluated = jexlExpression.evaluate(jexlContext);
+                if (evaluated != null) {
+                    result = evaluated.toString();
+                }
+            } catch (Exception e) {
+                LOG.error("Error while evaluating JEXL expression: " + expression, e);
+            }
+        } else {
+            LOG.debug("Expression not provided or invalid context");
+        }
+
+        return result;
+    }
+
+    public static JexlContext addFieldsToContext(final Object object, final JexlContext jexlContext) {
+        JexlContext context = jexlContext == null ? new MapContext() : jexlContext;
+
+        try {
+            for (PropertyDescriptor desc : Introspector.getBeanInfo(object.getClass()).getPropertyDescriptors()) {
+                final Class<?> type = desc.getPropertyType();
+                final String fieldName = desc.getName();
+
+                if ((!fieldName.startsWith("pc"))
+                        && (!ArrayUtils.contains(IGNORE_FIELDS, fieldName))
+                        && (!Iterable.class.isAssignableFrom(type))
+                        && (!type.isArray())) {
+                    try {
+                        final Method getter = desc.getReadMethod();
+
+                        final Object fieldValue;
+
+                        if (getter == null) {
+                            final Field field = object.getClass().getDeclaredField(fieldName);
+                            field.setAccessible(true);
+                            fieldValue = field.get(object);
+                        } else {
+                            fieldValue = getter.invoke(object);
+                        }
+
+                        context.set(fieldName, fieldValue == null
+                                ? StringUtils.EMPTY
+                                : (type.equals(Date.class)
+                                        ? DataFormat.format((Date) fieldValue, false)
+                                        : fieldValue));
+
+                        LOG.debug("Add field {} with value {}", fieldName, fieldValue);
+
+                    } catch (Exception iae) {
+                        LOG.error("Reading '{}' value error", fieldName, iae);
+                    }
+                }
+            }
+        } catch (IntrospectionException ie) {
+            LOG.error("Reading class attributes error", ie);
+        }
+
+        return context;
+    }
+
+    public static JexlContext addAttrsToContext(final Collection<? extends PlainAttr> attrs,
+            final JexlContext jexlContext) {
+
+        JexlContext context = jexlContext == null
+                ? new MapContext()
+                : jexlContext;
+
+        for (PlainAttr attr : attrs) {
+            if (attr.getSchema() != null) {
+                List<String> attrValues = attr.getValuesAsStrings();
+                String expressionValue = attrValues.isEmpty()
+                        ? StringUtils.EMPTY
+                        : attrValues.get(0);
+
+                LOG.debug("Add attribute {} with value {}", attr.getSchema().getKey(), expressionValue);
+
+                context.set(attr.getSchema().getKey(), expressionValue);
+            }
+        }
+
+        return context;
+    }
+
+    public static JexlContext addDerAttrsToContext(final Collection<? extends DerAttr> derAttrs,
+            final Collection<? extends PlainAttr> attrs, final JexlContext jexlContext) {
+
+        JexlContext context = jexlContext == null
+                ? new MapContext()
+                : jexlContext;
+
+        for (DerAttr derAttr : derAttrs) {
+            if (derAttr.getSchema() != null) {
+                String expressionValue = derAttr.getValue(attrs);
+                if (expressionValue == null) {
+                    expressionValue = StringUtils.EMPTY;
+                }
+
+                LOG.debug("Add derived attribute {} with value {}", derAttr.getSchema().getKey(), expressionValue);
+
+                context.set(derAttr.getSchema().getKey(), expressionValue);
+            }
+        }
+
+        return context;
+    }
+
+    public static JexlContext addVirAttrsToContext(final Collection<? extends VirAttr> virAttrs,
+            final JexlContext jexlContext) {
+
+        JexlContext context = jexlContext == null
+                ? new MapContext()
+                : jexlContext;
+
+        for (VirAttr virAttr : virAttrs) {
+            if (virAttr.getSchema() != null) {
+                List<String> attrValues = virAttr.getValues();
+                String expressionValue = attrValues.isEmpty()
+                        ? StringUtils.EMPTY
+                        : attrValues.get(0);
+
+                LOG.debug("Add virtual attribute {} with value {}", virAttr.getSchema().getKey(), expressionValue);
+
+                context.set(virAttr.getSchema().getKey(), expressionValue);
+            }
+        }
+
+        return context;
+    }
+
+    public static boolean evaluateMandatoryCondition(
+            final String mandatoryCondition, final Attributable<?, ?, ?> attributable) {
+
+        JexlContext jexlContext = new MapContext();
+        addAttrsToContext(attributable.getPlainAttrs(), jexlContext);
+        addDerAttrsToContext(attributable.getDerAttrs(), attributable.getPlainAttrs(), jexlContext);
+        addVirAttrsToContext(attributable.getVirAttrs(), jexlContext);
+
+        return Boolean.parseBoolean(evaluate(mandatoryCondition, jexlContext));
+    }
+
+    public static String evaluate(final String expression,
+            final Attributable<?, ?, ?> attributable, final Collection<? extends PlainAttr> attributes) {
+
+        final JexlContext jexlContext = new MapContext();
+        JexlUtil.addAttrsToContext(attributes, jexlContext);
+        JexlUtil.addFieldsToContext(attributable, jexlContext);
+
+        // Evaluate expression using the context prepared before
+        return evaluate(expression, jexlContext);
+    }
+
+    public static String evaluate(final String expression, final AbstractAttributableTO attributableTO) {
+        final JexlContext context = new MapContext();
+
+        addFieldsToContext(attributableTO, context);
+
+        for (AttrTO plainAttr : attributableTO.getPlainAttrs()) {
+            List<String> values = plainAttr.getValues();
+            String expressionValue = values.isEmpty()
+                    ? StringUtils.EMPTY
+                    : values.get(0);
+
+            LOG.debug("Add plain attribute {} with value {}", plainAttr.getSchema(), expressionValue);
+
+            context.set(plainAttr.getSchema(), expressionValue);
+        }
+        for (AttrTO derAttr : attributableTO.getDerAttrs()) {
+            List<String> values = derAttr.getValues();
+            String expressionValue = values.isEmpty()
+                    ? StringUtils.EMPTY
+                    : values.get(0);
+
+            LOG.debug("Add derived attribute {} with value {}", derAttr.getSchema(), expressionValue);
+
+            context.set(derAttr.getSchema(), expressionValue);
+        }
+        for (AttrTO virAttr : attributableTO.getVirAttrs()) {
+            List<String> values = virAttr.getValues();
+            String expressionValue = values.isEmpty()
+                    ? StringUtils.EMPTY
+                    : values.get(0);
+
+            LOG.debug("Add virtual attribute {} with value {}", virAttr.getSchema(), expressionValue);
+
+            context.set(virAttr.getSchema(), expressionValue);
+        }
+
+        // Evaluate expression using the context prepared before
+        return evaluate(expression, context);
+    }
+
+    /**
+     * Private default constructor, for static-only classes.
+     */
+    private JexlUtil() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyEnforcer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyEnforcer.java
new file mode 100644
index 0000000..0a4bdd3
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyEnforcer.java
@@ -0,0 +1,101 @@
+/*
+ * 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.policy;
+
+import java.util.regex.Pattern;
+import org.apache.syncope.common.lib.types.AccountPolicySpec;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AccountPolicyEnforcer implements PolicyEnforcer<AccountPolicySpec, User> {
+
+    private static final Pattern DEFAULT_PATTERN = Pattern.compile("[a-zA-Z0-9-_@. ]+");
+
+    @Autowired(required = false)
+    private UserSuspender userSuspender;
+
+    @Override
+    public void enforce(final AccountPolicySpec policy, final PolicyType type, final User user)
+            throws AccountPolicyException, PolicyEnforceException {
+
+        if (user.getUsername() == null) {
+            throw new PolicyEnforceException("Invalid account");
+        }
+
+        if (policy == null) {
+            throw new PolicyEnforceException("Invalid policy");
+        }
+
+        // check min length
+        if (policy.getMinLength() > 0 && policy.getMinLength() > user.getUsername().length()) {
+            throw new AccountPolicyException("Username too short");
+        }
+
+        // check max length
+        if (policy.getMaxLength() > 0 && policy.getMaxLength() < user.getUsername().length()) {
+            throw new AccountPolicyException("Username too long");
+        }
+
+        // check words not permitted
+        for (String word : policy.getWordsNotPermitted()) {
+            if (user.getUsername().contains(word)) {
+                throw new AccountPolicyException("Used word(s) not permitted");
+            }
+        }
+
+        // check case
+        if (policy.isAllUpperCase() && !user.getUsername().equals(user.getUsername().toUpperCase())) {
+            throw new AccountPolicyException("No lowercase characters permitted");
+        }
+        if (policy.isAllLowerCase() && !user.getUsername().equals(user.getUsername().toLowerCase())) {
+            throw new AccountPolicyException("No uppercase characters permitted");
+        }
+
+        // check pattern
+        Pattern pattern = (policy.getPattern() == null) ? DEFAULT_PATTERN : Pattern.compile(policy.getPattern());
+        if (!pattern.matcher(user.getUsername()).matches()) {
+            throw new AccountPolicyException("Username does not match pattern");
+        }
+
+        // check prefix
+        for (String prefix : policy.getPrefixesNotPermitted()) {
+            if (user.getUsername().startsWith(prefix)) {
+                throw new AccountPolicyException("Prefix not permitted");
+            }
+        }
+
+        // check suffix
+        for (String suffix : policy.getSuffixesNotPermitted()) {
+            if (user.getUsername().endsWith(suffix)) {
+                throw new AccountPolicyException("Suffix not permitted");
+            }
+        }
+
+        // check for subsequent failed logins
+        if (userSuspender != null
+                && user.getFailedLogins() != null && policy.getPermittedLoginRetries() > 0
+                && user.getFailedLogins() > policy.getPermittedLoginRetries() && !user.isSuspended()) {
+
+            userSuspender.suspend(user, policy.isPropagateSuspension());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyException.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyException.java
new file mode 100644
index 0000000..7173f0e
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.policy;
+
+public class AccountPolicyException extends PolicyException {
+
+    private static final long serialVersionUID = 2779416455067691813L;
+
+    public AccountPolicyException() {
+        super();
+    }
+
+    public AccountPolicyException(final String message) {
+        super(message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyEnforcer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyEnforcer.java
new file mode 100644
index 0000000..ff0a3f6
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyEnforcer.java
@@ -0,0 +1,202 @@
+/*
+ * 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.policy;
+
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PasswordPolicyEnforcer implements PolicyEnforcer<PasswordPolicySpec, User> {
+
+    /* (non-Javadoc)
+     * @see
+     * org.apache.syncope.core.policy.PasswordPolicyEnforcer#enforce(org.apache.syncope.common.types.PasswordPolicySpec,
+     * org.apache.syncope.common.types.PolicyType, java.lang.String)
+     */
+    @Override
+    public void enforce(final PasswordPolicySpec policy, final PolicyType type, final User user)
+            throws PasswordPolicyException, PolicyEnforceException {
+
+        final String clearPassword = user.getClearPassword();
+        final String password = user.getPassword();
+
+        if (policy == null) {
+            throw new PolicyEnforceException("Invalid policy");
+        }
+
+        if (password == null && !policy.isAllowNullPassword()) {
+            throw new PolicyEnforceException("Password must not be null and must be stored internally");
+        } else if (password != null && clearPassword != null) {
+            // check length
+            if (policy.getMinLength() > 0 && policy.getMinLength() > clearPassword.length()) {
+                throw new PasswordPolicyException("Password too short");
+            }
+
+            if (policy.getMaxLength() > 0 && policy.getMaxLength() < clearPassword.length()) {
+                throw new PasswordPolicyException("Password too long");
+            }
+
+            // check words not permitted
+            for (String word : policy.getWordsNotPermitted()) {
+                if (clearPassword.contains(word)) {
+                    throw new PasswordPolicyException("Used word(s) not permitted");
+                }
+            }
+
+            // check digits occurrence
+            if (policy.isDigitRequired() && !checkForDigit(clearPassword)) {
+                throw new PasswordPolicyException("Password must contain digit(s)");
+            }
+
+            // check lowercase alphabetic characters occurrence
+            if (policy.isLowercaseRequired() && !checkForLowercase(clearPassword)) {
+                throw new PasswordPolicyException("Password must contain lowercase alphabetic character(s)");
+            }
+
+            // check uppercase alphabetic characters occurrence
+            if (policy.isUppercaseRequired() && !checkForUppercase(clearPassword)) {
+                throw new PasswordPolicyException("Password must contain uppercase alphabetic character(s)");
+            }
+
+            // check prefix
+            for (String prefix : policy.getPrefixesNotPermitted()) {
+                if (clearPassword.startsWith(prefix)) {
+                    throw new PasswordPolicyException("Prefix not permitted");
+                }
+            }
+
+            // check suffix
+            for (String suffix : policy.getSuffixesNotPermitted()) {
+                if (clearPassword.endsWith(suffix)) {
+                    throw new PasswordPolicyException("Suffix not permitted");
+                }
+            }
+
+            // check digit first occurrence
+            if (policy.isMustStartWithDigit() && !checkForFirstDigit(clearPassword)) {
+                throw new PasswordPolicyException("Password must start with a digit");
+            }
+
+            if (policy.isMustntStartWithDigit() && checkForFirstDigit(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't start with a digit");
+            }
+
+            // check digit last occurrence
+            if (policy.isMustEndWithDigit() && !checkForLastDigit(clearPassword)) {
+                throw new PasswordPolicyException("Password must end with a digit");
+            }
+
+            if (policy.isMustntEndWithDigit() && checkForLastDigit(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't end with a digit");
+            }
+
+            // check alphanumeric characters occurence
+            if (policy.isAlphanumericRequired() && !checkForAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must contain alphanumeric character(s)");
+            }
+
+            // check non alphanumeric characters occurence
+            if (policy.isNonAlphanumericRequired() && !checkForNonAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must contain non-alphanumeric character(s)");
+            }
+
+            // check alphanumeric character first occurrence
+            if (policy.isMustStartWithAlpha() && !checkForFirstAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must start with an alphanumeric character");
+            }
+
+            if (policy.isMustntStartWithAlpha() && checkForFirstAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't start with an alphanumeric character");
+            }
+
+            // check alphanumeric character last occurrence
+            if (policy.isMustEndWithAlpha() && !checkForLastAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must end with an alphanumeric character");
+            }
+
+            if (policy.isMustntEndWithAlpha() && checkForLastAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't end with an alphanumeric character");
+            }
+
+            // check non alphanumeric character first occurrence
+            if (policy.isMustStartWithNonAlpha() && !checkForFirstNonAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must start with a non-alphanumeric character");
+            }
+
+            if (policy.isMustntStartWithNonAlpha() && checkForFirstNonAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't start with a non-alphanumeric character");
+            }
+
+            // check non alphanumeric character last occurrence
+            if (policy.isMustEndWithNonAlpha() && !checkForLastNonAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must end with a non-alphanumeric character");
+            }
+
+            if (policy.isMustntEndWithNonAlpha() && checkForLastNonAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't end with a non-alphanumeric character");
+            }
+        }
+    }
+
+    private boolean checkForDigit(final String str) {
+        return PolicyPattern.DIGIT.matcher(str).matches();
+    }
+
+    private boolean checkForLowercase(final String str) {
+        return PolicyPattern.ALPHA_LOWERCASE.matcher(str).matches();
+    }
+
+    private boolean checkForUppercase(final String str) {
+        return PolicyPattern.ALPHA_UPPERCASE.matcher(str).matches();
+    }
+
+    private boolean checkForFirstDigit(final String str) {
+        return PolicyPattern.FIRST_DIGIT.matcher(str).matches();
+    }
+
+    private boolean checkForLastDigit(final String str) {
+        return PolicyPattern.LAST_DIGIT.matcher(str).matches();
+    }
+
+    private boolean checkForAlphanumeric(final String str) {
+        return PolicyPattern.ALPHANUMERIC.matcher(str).matches();
+    }
+
+    private boolean checkForFirstAlphanumeric(final String str) {
+        return PolicyPattern.FIRST_ALPHANUMERIC.matcher(str).matches();
+    }
+
+    private boolean checkForLastAlphanumeric(final String str) {
+        return PolicyPattern.LAST_ALPHANUMERIC.matcher(str).matches();
+    }
+
+    private boolean checkForNonAlphanumeric(final String str) {
+        return PolicyPattern.NON_ALPHANUMERIC.matcher(str).matches();
+    }
+
+    private boolean checkForFirstNonAlphanumeric(final String str) {
+        return PolicyPattern.FIRST_NON_ALPHANUMERIC.matcher(str).matches();
+    }
+
+    private boolean checkForLastNonAlphanumeric(final String str) {
+        return PolicyPattern.LAST_NON_ALPHANUMERIC.matcher(str).matches();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyException.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyException.java
new file mode 100644
index 0000000..866e8ff
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.policy;
+
+public class PasswordPolicyException extends PolicyException {
+
+    private static final long serialVersionUID = 8072104484395278469L;
+
+    public PasswordPolicyException() {
+        super();
+    }
+
+    public PasswordPolicyException(final String message) {
+        super(message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforceException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforceException.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforceException.java
new file mode 100644
index 0000000..c4d2aac
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforceException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.policy;
+
+public class PolicyEnforceException extends PolicyException {
+
+    private static final long serialVersionUID = 3247084727383061069L;
+
+    public PolicyEnforceException() {
+        super();
+    }
+
+    public PolicyEnforceException(final String message) {
+        super(message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforcer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforcer.java
new file mode 100644
index 0000000..b057773
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforcer.java
@@ -0,0 +1,30 @@
+/*
+ * 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.policy;
+
+import java.io.InvalidObjectException;
+
+import org.apache.syncope.common.lib.types.PolicySpec;
+import org.apache.syncope.common.lib.types.PolicyType;
+
+public interface PolicyEnforcer<T extends PolicySpec, E> {
+
+    void enforce(final T policy, final PolicyType type, final E object)
+            throws InvalidObjectException, PolicyException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEvaluator.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEvaluator.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEvaluator.java
new file mode 100644
index 0000000..58b31b2
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEvaluator.java
@@ -0,0 +1,109 @@
+/*
+ * 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.policy;
+
+import java.util.List;
+import org.apache.syncope.common.lib.types.AccountPolicySpec;
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.common.lib.types.PolicySpec;
+import org.apache.syncope.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.Policy;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PolicyEvaluator {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(PolicyEvaluator.class);
+
+    @SuppressWarnings("unchecked")
+    public <T extends PolicySpec> T evaluate(final Policy policy, final Attributable<?, ?, ?> attributable) {
+        if (policy == null) {
+            return null;
+        }
+
+        T result = null;
+        switch (policy.getType()) {
+            case PASSWORD:
+            case GLOBAL_PASSWORD:
+                final PasswordPolicySpec ppSpec = policy.getSpecification(PasswordPolicySpec.class);
+                final PasswordPolicySpec evaluatedPPSpec = new PasswordPolicySpec();
+
+                BeanUtils.copyProperties(ppSpec, evaluatedPPSpec, new String[] { "schemasNotPermitted" });
+
+                for (String schema : ppSpec.getSchemasNotPermitted()) {
+                    PlainAttr attr = attributable.getPlainAttr(schema);
+                    if (attr != null) {
+                        List<String> values = attr.getValuesAsStrings();
+                        if (values != null && !values.isEmpty()) {
+                            evaluatedPPSpec.getWordsNotPermitted().add(values.get(0));
+                        }
+                    }
+                }
+
+                // Password history verification and update
+                if (!(attributable instanceof User)) {
+                    LOG.error("Cannot check previous passwords. attributable is not a user object: {}",
+                            attributable.getClass().getName());
+                    result = (T) evaluatedPPSpec;
+                    break;
+                }
+                User user = (User) attributable;
+                if (user.verifyPasswordHistory(user.getClearPassword(), ppSpec.getHistoryLength())) {
+                    evaluatedPPSpec.getWordsNotPermitted().add(user.getClearPassword());
+                }
+                result = (T) evaluatedPPSpec;
+                break;
+
+            case ACCOUNT:
+            case GLOBAL_ACCOUNT:
+                final AccountPolicySpec spec = policy.getSpecification(AccountPolicySpec.class);
+                final AccountPolicySpec accountPolicy = new AccountPolicySpec();
+
+                BeanUtils.copyProperties(spec, accountPolicy, new String[] { "schemasNotPermitted" });
+
+                for (String schema : spec.getSchemasNotPermitted()) {
+                    PlainAttr attr = attributable.getPlainAttr(schema);
+                    if (attr != null) {
+                        List<String> values = attr.getValuesAsStrings();
+                        if (values != null && !values.isEmpty()) {
+                            accountPolicy.getWordsNotPermitted().add(values.get(0));
+                        }
+                    }
+                }
+
+                result = (T) accountPolicy;
+                break;
+
+            case SYNC:
+            case GLOBAL_SYNC:
+            default:
+                result = null;
+        }
+
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyException.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyException.java
new file mode 100644
index 0000000..622d2f0
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.policy;
+
+public class PolicyException extends RuntimeException {
+
+    private static final long serialVersionUID = -6082115004491662910L;
+
+    public PolicyException() {
+        super();
+    }
+
+    public PolicyException(final String message) {
+        super(message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyPattern.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyPattern.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyPattern.java
new file mode 100644
index 0000000..9089995
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyPattern.java
@@ -0,0 +1,50 @@
+/*
+ * 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.policy;
+
+import java.util.regex.Pattern;
+
+public final class PolicyPattern {
+
+    public static final Pattern DIGIT = Pattern.compile(".*\\d+.*");
+
+    public static final Pattern ALPHA_LOWERCASE = Pattern.compile(".*[a-z]+.*");
+
+    public static final Pattern ALPHA_UPPERCASE = Pattern.compile(".*[A-Z]+.*");
+
+    public static final Pattern FIRST_DIGIT = Pattern.compile("\\d.*");
+
+    public static final Pattern LAST_DIGIT = Pattern.compile(".*\\d");
+
+    public static final Pattern ALPHANUMERIC = Pattern.compile(".*\\w.*");
+
+    public static final Pattern FIRST_ALPHANUMERIC = Pattern.compile("\\w.*");
+
+    public static final Pattern LAST_ALPHANUMERIC = Pattern.compile(".*\\w");
+
+    public static final Pattern NON_ALPHANUMERIC = Pattern.compile(".*\\W.*");
+
+    public static final Pattern FIRST_NON_ALPHANUMERIC = Pattern.compile("\\W.*");
+
+    public static final Pattern LAST_NON_ALPHANUMERIC = Pattern.compile(".*\\W");
+
+    private PolicyPattern() {
+        // private constructor for static utility class
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/UserSuspender.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/UserSuspender.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/UserSuspender.java
new file mode 100644
index 0000000..3b4da6a
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/UserSuspender.java
@@ -0,0 +1,26 @@
+/*
+ * 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.policy;
+
+import org.apache.syncope.persistence.api.entity.user.User;
+
+public interface UserSuspender {
+
+    void suspend(User user, boolean propagateSuspension);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeDeserializer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeDeserializer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeDeserializer.java
new file mode 100644
index 0000000..b1a3790
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeDeserializer.java
@@ -0,0 +1,84 @@
+/*
+ * 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.serialization;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.identityconnectors.common.Base64;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.Name;
+import org.identityconnectors.framework.common.objects.Uid;
+
+class AttributeDeserializer extends JsonDeserializer<Attribute> {
+
+    @Override
+    public Attribute deserialize(final JsonParser jp, final DeserializationContext ctx)
+            throws IOException, JsonProcessingException {
+
+        ObjectNode tree = jp.readValueAsTree();
+
+        String name = tree.get("name").asText();
+
+        List<Object> values = new ArrayList<Object>();
+        for (Iterator<JsonNode> itor = tree.get("value").iterator(); itor.hasNext();) {
+            JsonNode node = itor.next();
+            if (node.isNull()) {
+                values.add(null);
+            } else if (node.isObject()) {
+                values.add(((ObjectNode) node).traverse(jp.getCodec()).readValueAs(GuardedString.class));
+            } else if (node.isBoolean()) {
+                values.add(node.asBoolean());
+            } else if (node.isDouble()) {
+                values.add(node.asDouble());
+            } else if (node.isLong()) {
+                values.add(node.asLong());
+            } else if (node.isInt()) {
+                values.add(node.asInt());
+            } else {
+                String text = node.asText();
+                if (text.startsWith(AttributeSerializer.BYTE_ARRAY_PREFIX)
+                        && text.endsWith(AttributeSerializer.BYTE_ARRAY_SUFFIX)) {
+
+                    values.add(Base64.decode(StringUtils.substringBetween(
+                            text, AttributeSerializer.BYTE_ARRAY_PREFIX, AttributeSerializer.BYTE_ARRAY_SUFFIX)));
+                } else {
+                    values.add(text);
+                }
+            }
+        }
+
+        return Uid.NAME.equals(name)
+                ? new Uid(values.isEmpty() || values.get(0) == null ? null : values.get(0).toString())
+                : Name.NAME.equals(name)
+                ? new Name(values.isEmpty() || values.get(0) == null ? null : values.get(0).toString())
+                : AttributeBuilder.build(name, values);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeSerializer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeSerializer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeSerializer.java
new file mode 100644
index 0000000..2a75eec
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeSerializer.java
@@ -0,0 +1,78 @@
+/*
+ * 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.serialization;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+import org.identityconnectors.common.Base64;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class AttributeSerializer extends JsonSerializer<Attribute> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AttributeSerializer.class);
+
+    public static final String BYTE_ARRAY_PREFIX = "<binary>";
+
+    public static final String BYTE_ARRAY_SUFFIX = "</binary>";
+
+    @Override
+    public void serialize(final Attribute source, final JsonGenerator jgen, final SerializerProvider sp)
+            throws IOException, JsonProcessingException {
+
+        jgen.writeStartObject();
+
+        jgen.writeStringField("name", source.getName());
+
+        jgen.writeFieldName("value");
+        if (source.getValue() == null) {
+            jgen.writeNull();
+        } else {
+            jgen.writeStartArray();
+            for (Object value : source.getValue()) {
+                if (value == null) {
+                    jgen.writeNull();
+                } else if (value instanceof GuardedString) {
+                    jgen.writeObject(value);
+                } else if (value instanceof Integer) {
+                    jgen.writeNumber((Integer) value);
+                } else if (value instanceof Long) {
+                    jgen.writeNumber((Long) value);
+                } else if (value instanceof Double) {
+                    jgen.writeNumber((Double) value);
+                } else if (value instanceof Boolean) {
+                    jgen.writeBoolean((Boolean) value);
+                } else if (value instanceof byte[]) {
+                    jgen.writeString(BYTE_ARRAY_PREFIX + Base64.encode((byte[]) value) + BYTE_ARRAY_SUFFIX);
+                } else {
+                    jgen.writeString(value.toString());
+                }
+            }
+            jgen.writeEndArray();
+        }
+
+        jgen.writeEndObject();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/GuardedStringDeserializer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/GuardedStringDeserializer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/GuardedStringDeserializer.java
new file mode 100644
index 0000000..c05c94a
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/GuardedStringDeserializer.java
@@ -0,0 +1,94 @@
+/*
+ * 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.serialization;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import org.identityconnectors.common.Base64;
+import org.identityconnectors.common.security.EncryptorFactory;
+import org.identityconnectors.common.security.GuardedString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class GuardedStringDeserializer extends JsonDeserializer<GuardedString> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(GuardedStringDeserializer.class);
+
+    @Override
+    public GuardedString deserialize(final JsonParser jp, final DeserializationContext ctx)
+            throws IOException, JsonProcessingException {
+
+        ObjectNode tree = jp.readValueAsTree();
+
+        boolean readOnly = false;
+        if (tree.has("readOnly")) {
+            readOnly = tree.get("readOnly").asBoolean();
+        }
+        boolean disposed = false;
+        if (tree.has("disposed")) {
+            disposed = tree.get("disposed").asBoolean();
+        }
+        byte[] encryptedBytes = null;
+        if (tree.has("encryptedBytes")) {
+            encryptedBytes = Base64.decode(tree.get("encryptedBytes").asText());
+        }
+        String base64SHA1Hash = null;
+        if (tree.has("base64SHA1Hash")) {
+            base64SHA1Hash = tree.get("base64SHA1Hash").asText();
+        }
+
+        final byte[] clearBytes = EncryptorFactory.getInstance().getDefaultEncryptor().decrypt(encryptedBytes);
+
+        GuardedString dest = new GuardedString(new String(clearBytes).toCharArray());
+
+        try {
+            Field field = GuardedString.class.getDeclaredField("readOnly");
+            field.setAccessible(true);
+            field.setBoolean(dest, readOnly);
+        } catch (Exception e) {
+            LOG.error("Could not set field value to {}", readOnly, e);
+        }
+
+        try {
+            Field field = GuardedString.class.getDeclaredField("disposed");
+            field.setAccessible(true);
+            field.setBoolean(dest, disposed);
+        } catch (Exception e) {
+            LOG.error("Could not set field value to {}", disposed, e);
+        }
+
+        if (base64SHA1Hash != null) {
+            try {
+                Field field = GuardedString.class.getDeclaredField("base64SHA1Hash");
+                field.setAccessible(true);
+                field.set(dest, base64SHA1Hash);
+            } catch (Exception e) {
+                LOG.error("Could not set field value to {}", base64SHA1Hash, e);
+            }
+        }
+
+        return dest;
+    }
+
+}