keycloak-uncached

Changes

forms/pom.xml 6(+0 -6)

model/mongo/pom.xml 77(+77 -0)

model/pom.xml 1(+1 -0)

pom.xml 85(+80 -5)

services/pom.xml 23(+21 -2)

testsuite/pom.xml 217(+7 -210)

Details

diff --git a/examples/as7-eap-demo/server/pom.xml b/examples/as7-eap-demo/server/pom.xml
index 7027dc5..3282368 100755
--- a/examples/as7-eap-demo/server/pom.xml
+++ b/examples/as7-eap-demo/server/pom.xml
@@ -118,7 +118,14 @@
         <dependency>
             <groupId>com.h2database</groupId>
             <artifactId>h2</artifactId>
-            <version>1.3.161</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mongodb</groupId>
+            <artifactId>mongo-java-driver</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.flapdoodle.embed</groupId>
+            <artifactId>de.flapdoodle.embed.mongo</artifactId>
         </dependency>
         <dependency>
             <groupId>junit</groupId>
diff --git a/examples/as7-eap-demo/server/src/main/webapp/WEB-INF/web.xml b/examples/as7-eap-demo/server/src/main/webapp/WEB-INF/web.xml
index 6829b0e..fafd744 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/WEB-INF/web.xml
+++ b/examples/as7-eap-demo/server/src/main/webapp/WEB-INF/web.xml
@@ -21,6 +21,10 @@
         <async-supported>true</async-supported>
     </servlet>
 
+    <listener>
+        <listener-class>org.keycloak.services.listeners.MongoRunnerListener</listener-class>
+    </listener>
+
     <filter>
         <filter-name>Keycloak Session Management</filter-name>
         <filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class>

forms/pom.xml 6(+0 -6)

diff --git a/forms/pom.xml b/forms/pom.xml
index 6d0dec8..0af1892 100755
--- a/forms/pom.xml
+++ b/forms/pom.xml
@@ -46,12 +46,6 @@
 		<dependency>
 			<groupId>org.jboss.spec.javax.servlet</groupId>
 			<artifactId>jboss-servlet-api_3.0_spec</artifactId>
-			<version>1.0.2.Final</version>
-		</dependency>
-		<dependency>
-			<groupId>javax.faces</groupId>
-			<artifactId>jsf-api</artifactId>
-			<version>2.1</version>
 		</dependency>
 		<dependency>
       		<groupId>com.google.zxing</groupId>
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java
new file mode 100644
index 0000000..9119282
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java
@@ -0,0 +1,15 @@
+package org.keycloak.models.utils;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KeycloakSessionUtils {
+
+    private static AtomicLong counter = new AtomicLong(1);
+
+    public static String generateId() {
+        return counter.getAndIncrement() + "-" + System.currentTimeMillis();
+    }
+}

model/mongo/pom.xml 77(+77 -0)

diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml
new file mode 100644
index 0000000..537112a
--- /dev/null
+++ b/model/mongo/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-alpha-1</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-model-mongo</artifactId>
+    <name>Keycloak Model Mongo</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk16</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-common</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-idm-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mongodb</groupId>
+            <artifactId>mongo-java-driver</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>de.flapdoodle.embed</groupId>
+            <artifactId>de.flapdoodle.embed.mongo</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java
new file mode 100644
index 0000000..81546ba
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java
@@ -0,0 +1,37 @@
+package org.keycloak.models.mongo.api;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractAttributedNoSQLObject extends AbstractNoSQLObject implements AttributedNoSQLObject {
+
+    // Simple hashMap for now (no thread-safe)
+    private Map<String, String> attributes = new HashMap<String, String>();
+
+    @Override
+    public void setAttribute(String name, String value) {
+        attributes.put(name, value);
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        // attributes.remove(name);
+
+        // ensure that particular attribute has null value, so it will be deleted in DB. TODO: needs to be improved
+        attributes.put(name, null);
+    }
+
+    @Override
+    public String getAttribute(String name) {
+        return attributes.get(name);
+    }
+
+    @Override
+    public Map<String, String> getAttributes() {
+        return Collections.unmodifiableMap(attributes);
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java
new file mode 100644
index 0000000..837e5e4
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java
@@ -0,0 +1,12 @@
+package org.keycloak.models.mongo.api;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractNoSQLObject implements NoSQLObject {
+
+    @Override
+    public void afterRemove(NoSQL noSQL) {
+        // Empty by default
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java
new file mode 100644
index 0000000..45accd1
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java
@@ -0,0 +1,17 @@
+package org.keycloak.models.mongo.api;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface AttributedNoSQLObject extends NoSQLObject {
+
+    void setAttribute(String name, String value);
+
+    void removeAttribute(String name);
+
+    String getAttribute(String name);
+
+    Map<String, String> getAttributes();
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java
new file mode 100644
index 0000000..3bc62a5
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java
@@ -0,0 +1,37 @@
+package org.keycloak.models.mongo.api;
+
+import java.util.List;
+
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
+import org.picketlink.common.properties.Property;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface NoSQL {
+
+    /**
+     * Insert object if it's oid is null. Otherwise update
+     */
+    void saveObject(NoSQLObject object);
+
+    <T extends NoSQLObject> T loadObject(Class<T> type, String oid);
+
+    <T extends NoSQLObject> T loadSingleObject(Class<T> type, NoSQLQuery query);
+
+    <T extends NoSQLObject> List<T> loadObjects(Class<T> type, NoSQLQuery query);
+
+    // Object must have filled oid
+    void removeObject(NoSQLObject object);
+
+    void removeObject(Class<? extends NoSQLObject> type, String oid);
+
+    void removeObjects(Class<? extends NoSQLObject> type, NoSQLQuery query);
+
+    NoSQLQueryBuilder createQueryBuilder();
+
+    <S> void pushItemToList(NoSQLObject object, String listPropertyName, S itemToPush);
+
+    <S> void pullItemFromList(NoSQLObject object, String listPropertyName, S itemToPull);
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java
new file mode 100644
index 0000000..80b6332
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java
@@ -0,0 +1,21 @@
+package org.keycloak.models.mongo.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Target({TYPE})
+@Documented
+@Retention(RUNTIME)
+@Inherited
+public @interface NoSQLCollection {
+
+    String collectionName();
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java
new file mode 100644
index 0000000..3af69a7
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java
@@ -0,0 +1,20 @@
+package org.keycloak.models.mongo.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Target({METHOD, FIELD})
+@Documented
+@Retention(RUNTIME)
+public @interface NoSQLField {
+
+    // TODO: fieldName add lazy loading?
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java
new file mode 100644
index 0000000..06ed01e
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java
@@ -0,0 +1,18 @@
+package org.keycloak.models.mongo.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Target({METHOD, FIELD})
+@Documented
+@Retention(RUNTIME)
+public @interface NoSQLId {
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java
new file mode 100644
index 0000000..0242243
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java
@@ -0,0 +1,16 @@
+package org.keycloak.models.mongo.api;
+
+/**
+ * Base interface for object, which is persisted in NoSQL database
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface NoSQLObject {
+
+    /**
+     * Lifecycle callback, which is called after removal of this object from NoSQL database.
+     * It may be useful for triggering removal of wired objects.
+     */
+    void afterRemove(NoSQL noSQL);
+
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java
new file mode 100644
index 0000000..29cc0f3
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java
@@ -0,0 +1,26 @@
+package org.keycloak.models.mongo.api.query;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class NoSQLQuery {
+
+    private final Map<String, Object> queryAttributes;
+
+    NoSQLQuery(Map<String, Object> queryAttributes) {
+        this.queryAttributes = queryAttributes;
+    };
+
+    public Map<String, Object> getQueryAttributes() {
+        return Collections.unmodifiableMap(queryAttributes);
+    }
+
+    @Override
+    public String toString() {
+        return "NoSQLQuery [" + queryAttributes + "]";
+    }
+
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java
new file mode 100644
index 0000000..dcdb575
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java
@@ -0,0 +1,31 @@
+package org.keycloak.models.mongo.api.query;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class NoSQLQueryBuilder {
+
+    private Map<String, Object> queryAttributes = new HashMap<String, Object>();
+
+    protected NoSQLQueryBuilder() {};
+
+    public NoSQLQuery build() {
+        return new NoSQLQuery(queryAttributes);
+    }
+
+    public NoSQLQueryBuilder andCondition(String name, Object value) {
+        this.put(name, value);
+        return this;
+    }
+
+    public abstract NoSQLQueryBuilder inCondition(String name, List<?> values);
+
+    protected void put(String name, Object value) {
+        queryAttributes.put(name, value);
+    }
+
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java
new file mode 100644
index 0000000..a6b6c86
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java
@@ -0,0 +1,16 @@
+package org.keycloak.models.mongo.api.types;
+
+/**
+ * SPI object to convert object from application type to database type and vice versa. Shouldn't be directly used by application.
+ * Various converters should be registered in TypeConverter, which is main entry point to be used by application
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface Converter<T, S> {
+
+    S convertObject(T objectToConvert);
+
+    Class<? extends T> getConverterObjectType();
+
+    Class<S> getExpectedReturnType();
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java
new file mode 100644
index 0000000..a7c12c0
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java
@@ -0,0 +1,114 @@
+package org.keycloak.models.mongo.api.types;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.picketlink.common.reflection.Reflections;
+
+/**
+ * Registry of converters, which allow to convert application object to database objects. TypeConverter is main entry point to be used by application.
+ * Application can create instance of TypeConverter and then register required Converter objects.
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TypeConverter {
+
+    // TODO: Thread-safety support (maybe...)
+    // Converters of Application objects to DB objects
+    private Map<Class<?>, Converter<?, ?>> appObjectConverters = new HashMap<Class<?>, Converter<?, ?>>();
+
+    // Converters of DB objects to Application objects
+    private Map<Class<?>, Map<Class<?>, Converter<?, ?>>> dbObjectConverters = new HashMap<Class<?>, Map<Class<?>, Converter<?,?>>>();
+
+
+    /**
+     * Add converter for converting application objects to DB objects
+     *
+     * @param converter
+     */
+    public void addAppObjectConverter(Converter<?, ?> converter) {
+        appObjectConverters.put(converter.getConverterObjectType(), converter);
+    }
+
+
+    /**
+     * Add converter for converting DB objects to application objects
+     *
+     * @param converter
+     */
+    public void addDBObjectConverter(Converter<?, ?> converter) {
+        Class<?> dbObjectType = converter.getConverterObjectType();
+        Class<?> appObjectType = converter.getExpectedReturnType();
+        Map<Class<?>, Converter<?, ?>> appObjects = dbObjectConverters.get(dbObjectType);
+        if (appObjects == null) {
+            appObjects = new HashMap<Class<?>, Converter<?, ?>>();
+            dbObjectConverters.put(dbObjectType, appObjects);
+        }
+        appObjects.put(appObjectType, converter);
+    }
+
+
+    public <S> S convertDBObjectToApplicationObject(Object dbObject, Class<S> expectedApplicationObjectType) {
+        Class<?> dbObjectType = dbObject.getClass();
+        Converter<Object, S> converter;
+
+        Map<Class<?>, Converter<?, ?>> appObjects = dbObjectConverters.get(dbObjectType);
+        if (appObjects == null) {
+            throw new IllegalArgumentException("Not found any converters for type " + dbObjectType);
+        } else {
+            if (appObjects.size() == 1) {
+                converter = (Converter<Object, S>)appObjects.values().iterator().next();
+            } else {
+                // Try to find converter for requested application type
+                converter = (Converter<Object, S>)getAppConverterForType(expectedApplicationObjectType, appObjects);
+            }
+        }
+
+        if (converter == null) {
+            throw new IllegalArgumentException("Can't found converter for type " + dbObjectType + " and expectedApplicationType " + expectedApplicationObjectType);
+        }
+        /*if (!expectedApplicationObjectType.isAssignableFrom(converter.getExpectedReturnType())) {
+            throw new IllegalArgumentException("Converter " + converter + " has return type " + converter.getExpectedReturnType() +
+                    " but we need type " + expectedApplicationObjectType);
+        } */
+
+        return converter.convertObject(dbObject);
+    }
+
+
+    public <S> S convertApplicationObjectToDBObject(Object applicationObject, Class<S> expectedDBObjectType) {
+        Class<?> appObjectType = applicationObject.getClass();
+        Converter<Object, S> converter = (Converter<Object, S>)getAppConverterForType(appObjectType, appObjectConverters);
+        if (converter == null) {
+            throw new IllegalArgumentException("Can't found converter for type " + appObjectType + " in registered appObjectConverters");
+        }
+        if (!expectedDBObjectType.isAssignableFrom(converter.getExpectedReturnType())) {
+            throw new IllegalArgumentException("Converter " + converter + " has return type " + converter.getExpectedReturnType() +
+                    " but we need type " + expectedDBObjectType);
+        }
+        return converter.convertObject(applicationObject);
+    }
+
+    // Try to find converter for given type or all it's supertypes
+    private static Converter<Object, ?> getAppConverterForType(Class<?> appObjectType, Map<Class<?>, Converter<?, ?>> appObjectConverters) {
+        Converter<Object, ?> converter = (Converter<Object, ?>)appObjectConverters.get(appObjectType);
+        if (converter != null) {
+            return converter;
+        } else {
+            Class<?>[] interfaces = appObjectType.getInterfaces();
+            for (Class<?> interface1 : interfaces) {
+                converter = getAppConverterForType(interface1, appObjectConverters);
+                if (converter != null) {
+                    return converter;
+                }
+            }
+
+            Class<?> superType = appObjectType.getSuperclass();
+            if (superType != null) {
+                return getAppConverterForType(superType, appObjectConverters);
+            } else {
+                return null;
+            }
+        }
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBImpl.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBImpl.java
new file mode 100644
index 0000000..09ca78e
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBImpl.java
@@ -0,0 +1,324 @@
+package org.keycloak.models.mongo.impl;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import com.mongodb.BasicDBList;
+import com.mongodb.BasicDBObject;
+import com.mongodb.DB;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+import org.bson.types.ObjectId;
+import org.jboss.logging.Logger;
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.NoSQLCollection;
+import org.keycloak.models.mongo.api.NoSQLField;
+import org.keycloak.models.mongo.api.NoSQLId;
+import org.keycloak.models.mongo.api.NoSQLObject;
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
+import org.keycloak.models.mongo.api.types.Converter;
+import org.keycloak.models.mongo.api.types.TypeConverter;
+import org.keycloak.models.mongo.impl.types.EnumToStringConverter;
+import org.keycloak.models.mongo.impl.types.ListConverter;
+import org.keycloak.models.mongo.impl.types.BasicDBListConverter;
+import org.keycloak.models.mongo.impl.types.BasicDBObjectConverter;
+import org.keycloak.models.mongo.impl.types.NoSQLObjectConverter;
+import org.keycloak.models.mongo.impl.types.SimpleConverter;
+import org.keycloak.models.mongo.impl.types.StringToEnumConverter;
+import org.picketlink.common.properties.Property;
+import org.picketlink.common.properties.query.AnnotatedPropertyCriteria;
+import org.picketlink.common.properties.query.PropertyQueries;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MongoDBImpl implements NoSQL {
+
+    private static final Class<?>[] SIMPLE_TYPES = { String.class, Integer.class, Boolean.class, Long.class, Double.class, Character.class, Date.class };
+
+    private final DB database;
+    private static final Logger logger = Logger.getLogger(MongoDBImpl.class);
+
+    private final TypeConverter typeConverter;
+    private ConcurrentMap<Class<? extends NoSQLObject>, ObjectInfo> objectInfoCache =
+            new ConcurrentHashMap<Class<? extends NoSQLObject>, ObjectInfo>();
+
+
+    public MongoDBImpl(DB database, boolean dropDatabaseOnStartup, Class<? extends NoSQLObject>[] managedDataTypes) {
+        this.database = database;
+
+        typeConverter = new TypeConverter();
+
+        for (Class<?> simpleConverterClass : SIMPLE_TYPES) {
+            SimpleConverter converter = new SimpleConverter(simpleConverterClass);
+            typeConverter.addAppObjectConverter(converter);
+            typeConverter.addDBObjectConverter(converter);
+        }
+
+        // Specific converter for ArrayList is added just for performance purposes to avoid recursive converter lookup (most of list impl will be ArrayList)
+        typeConverter.addAppObjectConverter(new ListConverter(typeConverter, ArrayList.class));
+        typeConverter.addAppObjectConverter(new ListConverter(typeConverter, List.class));
+        typeConverter.addDBObjectConverter(new BasicDBListConverter(typeConverter));
+
+        // Enum converters
+        typeConverter.addAppObjectConverter(new EnumToStringConverter());
+        typeConverter.addDBObjectConverter(new StringToEnumConverter());
+
+        for (Class<? extends NoSQLObject> type : managedDataTypes) {
+            getObjectInfo(type);
+            typeConverter.addAppObjectConverter(new NoSQLObjectConverter(this, typeConverter, type));
+            typeConverter.addDBObjectConverter(new BasicDBObjectConverter(this, typeConverter, type));
+        }
+
+        if (dropDatabaseOnStartup) {
+            this.database.dropDatabase();
+            logger.info("Database " + this.database.getName() + " dropped in MongoDB");
+        }
+    }
+
+
+    @Override
+    public void saveObject(NoSQLObject object) {
+        Class<? extends NoSQLObject> clazz = object.getClass();
+
+        // Find annotations for ID, for all the properties and for the name of the collection.
+        ObjectInfo objectInfo = getObjectInfo(clazz);
+
+        // Create instance of BasicDBObject and add all declared properties to it (properties with null value probably should be skipped)
+        BasicDBObject dbObject = typeConverter.convertApplicationObjectToDBObject(object, BasicDBObject.class);
+
+        DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName());
+
+        // Decide if we should insert or update (based on presence of oid property in original object)
+        Property<String> oidProperty = objectInfo.getOidProperty();
+        String currentId = oidProperty == null ? null : oidProperty.getValue(object);
+        if (currentId == null) {
+            dbCollection.insert(dbObject);
+
+            // Add oid to value of given object
+            if (oidProperty != null) {
+                oidProperty.setValue(object, dbObject.getString("_id"));
+            }
+        } else {
+            BasicDBObject query = new BasicDBObject("_id", new ObjectId(currentId));
+            dbCollection.update(query, dbObject);
+        }
+    }
+
+
+    @Override
+    public <T extends NoSQLObject> T loadObject(Class<T> type, String oid) {
+        DBCollection dbCollection = getDBCollectionForType(type);
+
+        BasicDBObject idQuery = new BasicDBObject("_id", new ObjectId(oid));
+        DBObject dbObject = dbCollection.findOne(idQuery);
+
+        return typeConverter.convertDBObjectToApplicationObject(dbObject, type);
+    }
+
+
+    @Override
+    public <T extends NoSQLObject> T loadSingleObject(Class<T> type, NoSQLQuery query) {
+        List<T> result = loadObjects(type, query);
+        if (result.size() > 1) {
+            throw new IllegalStateException("There are " + result.size() + " results for type=" + type + ", query=" + query + ". We expect just one");
+        } else if (result.size() == 1) {
+            return result.get(0);
+        } else {
+            // 0 results
+            return null;
+        }
+    }
+
+
+    @Override
+    public <T extends NoSQLObject> List<T> loadObjects(Class<T> type, NoSQLQuery query) {
+        DBCollection dbCollection = getDBCollectionForType(type);
+        BasicDBObject dbQuery = getDBQueryFromQuery(query);
+
+        DBCursor cursor = dbCollection.find(dbQuery);
+
+        return convertCursor(type, cursor);
+    }
+
+
+    @Override
+    public void removeObject(NoSQLObject object) {
+        Class<? extends NoSQLObject> type = object.getClass();
+        ObjectInfo objectInfo = getObjectInfo(type);
+
+        Property<String> idProperty = objectInfo.getOidProperty();
+        String oid = idProperty.getValue(object);
+
+        removeObject(type, oid);
+    }
+
+
+    @Override
+    public void removeObject(Class<? extends NoSQLObject> type, String oid) {
+        NoSQLObject found = loadObject(type, oid);
+        if (found == null) {
+            logger.warn("Object of type: " + type + ", oid: " + oid + " doesn't exist in MongoDB. Skip removal");
+        } else {
+            DBCollection dbCollection = getDBCollectionForType(type);
+            BasicDBObject dbQuery = new BasicDBObject("_id", new ObjectId(oid));
+            dbCollection.remove(dbQuery);
+            logger.info("Object of type: " + type + ", oid: " + oid + " removed from MongoDB.");
+
+            found.afterRemove(this);
+        }
+    }
+
+
+    @Override
+    public void removeObjects(Class<? extends NoSQLObject> type, NoSQLQuery query) {
+        List<? extends NoSQLObject> foundObjects = loadObjects(type, query);
+        if (foundObjects.size() == 0) {
+            logger.info("Not found any objects of type: " + type + ", query: " + query);
+        } else {
+            DBCollection dbCollection = getDBCollectionForType(type);
+            BasicDBObject dbQuery = getDBQueryFromQuery(query);
+            dbCollection.remove(dbQuery);
+            logger.info("Removed " + foundObjects.size() + " objects of type: " + type + ", query: " + query);
+
+            for (NoSQLObject found : foundObjects) {
+                found.afterRemove(this);
+            }
+        }
+    }
+
+
+    @Override
+    public NoSQLQueryBuilder createQueryBuilder() {
+        return new MongoDBQueryBuilder();
+    }
+
+
+    @Override
+    public <S> void pushItemToList(NoSQLObject object, String listPropertyName, S itemToPush) {
+        Class<? extends NoSQLObject> type = object.getClass();
+        ObjectInfo objectInfo = getObjectInfo(type);
+
+        Property<String> oidProperty = getObjectInfo(type).getOidProperty();
+        if (oidProperty == null) {
+            throw new IllegalArgumentException("List pushes not supported for properties without oid");
+        }
+
+        // Add item to list directly in this object
+        Property<Object> listProperty = objectInfo.getPropertyByName(listPropertyName);
+        if (listProperty == null) {
+            throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + object);
+        }
+
+        List<S> list = (List<S>)listProperty.getValue(object);
+        if (list == null) {
+            list = new ArrayList<S>();
+            listProperty.setValue(object, list);
+        }
+        list.add(itemToPush);
+
+        // Push item to DB. We always convert whole list, so it's not so optimal...
+        BasicDBList dbList = typeConverter.convertApplicationObjectToDBObject(list, BasicDBList.class);
+
+        BasicDBObject query = new BasicDBObject("_id", new ObjectId(oidProperty.getValue(object)));
+        BasicDBObject listObject = new BasicDBObject(listPropertyName, dbList);
+        BasicDBObject setCommand = new BasicDBObject("$set", listObject);
+        getDBCollectionForType(type).update(query, setCommand);
+    }
+
+
+    @Override
+    public <S> void pullItemFromList(NoSQLObject object, String listPropertyName, S itemToPull) {
+        Class<? extends NoSQLObject> type = object.getClass();
+        ObjectInfo objectInfo = getObjectInfo(type);
+
+        Property<String> oidProperty = getObjectInfo(type).getOidProperty();
+        if (oidProperty == null) {
+            throw new IllegalArgumentException("List pulls not supported for properties without oid");
+        }
+
+        // Remove item from list directly in this object
+        Property<Object> listProperty = objectInfo.getPropertyByName(listPropertyName);
+        if (listProperty == null) {
+            throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + object);
+        }
+        List<S> list = (List<S>)listProperty.getValue(object);
+
+        // If list is null, we skip both object and DB update
+        if (list != null) {
+            list.remove(itemToPull);
+
+            // Pull item from DB
+            Object dbItemToPull = typeConverter.convertApplicationObjectToDBObject(itemToPull, Object.class);
+            BasicDBObject query = new BasicDBObject("_id", new ObjectId(oidProperty.getValue(object)));
+            BasicDBObject pullObject = new BasicDBObject(listPropertyName, dbItemToPull);
+            BasicDBObject pullCommand = new BasicDBObject("$pull", pullObject);
+            getDBCollectionForType(type).update(query, pullCommand);
+        }
+    }
+
+    // Possibility to add user-defined converters
+    public void addAppObjectConverter(Converter<?, ?> converter) {
+        typeConverter.addAppObjectConverter(converter);
+    }
+
+    public void addDBObjectConverter(Converter<?, ?> converter) {
+        typeConverter.addDBObjectConverter(converter);
+    }
+
+    public ObjectInfo getObjectInfo(Class<? extends NoSQLObject> objectClass) {
+        ObjectInfo objectInfo = objectInfoCache.get(objectClass);
+        if (objectInfo == null) {
+            Property<String> idProperty = PropertyQueries.<String>createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLId.class)).getFirstResult();
+
+            List<Property<Object>> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLField.class)).getResultList();
+
+            NoSQLCollection classAnnotation = objectClass.getAnnotation(NoSQLCollection.class);
+
+            String dbCollectionName = classAnnotation==null ? null : classAnnotation.collectionName();
+            objectInfo = new ObjectInfo(objectClass, dbCollectionName, idProperty, properties);
+
+            ObjectInfo existing = objectInfoCache.putIfAbsent(objectClass, objectInfo);
+            if (existing != null) {
+                objectInfo = existing;
+            }
+        }
+
+        return objectInfo;
+    }
+
+    private <T extends NoSQLObject> List<T> convertCursor(Class<T> type, DBCursor cursor) {
+        List<T> result = new ArrayList<T>();
+
+        try {
+            for (DBObject dbObject : cursor) {
+                T converted = typeConverter.convertDBObjectToApplicationObject(dbObject, type);
+                result.add(converted);
+            }
+        } finally {
+            cursor.close();
+        }
+
+        return result;
+    }
+
+    private DBCollection getDBCollectionForType(Class<? extends NoSQLObject> type) {
+        ObjectInfo objectInfo = getObjectInfo(type);
+        return database.getCollection(objectInfo.getDbCollectionName());
+    }
+
+    private BasicDBObject getDBQueryFromQuery(NoSQLQuery query) {
+        Map<String, Object> queryAttributes = query.getQueryAttributes();
+        BasicDBObject dbQuery = new BasicDBObject();
+        for (Map.Entry<String, Object> queryAttr : queryAttributes.entrySet()) {
+            dbQuery.append(queryAttr.getKey(), queryAttr.getValue());
+        }
+        return dbQuery;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBQueryBuilder.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBQueryBuilder.java
new file mode 100644
index 0000000..f56c799
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBQueryBuilder.java
@@ -0,0 +1,38 @@
+package org.keycloak.models.mongo.impl;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.mongodb.BasicDBObject;
+import org.bson.types.ObjectId;
+import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MongoDBQueryBuilder extends NoSQLQueryBuilder {
+
+    protected MongoDBQueryBuilder() {};
+
+    @Override
+    public NoSQLQueryBuilder inCondition(String name, List<?> values) {
+        if (values == null) {
+            values = new LinkedList<Object>();
+        }
+
+        if ("_id".equals(name)) {
+            // we need to convert Strings to ObjectID
+            List<ObjectId> objIds = new ArrayList<ObjectId>();
+            for (Object object : values) {
+                ObjectId objectId = new ObjectId(object.toString());
+                objIds.add(objectId);
+            }
+            values = objIds;
+        }
+
+        BasicDBObject inObject = new BasicDBObject("$in", values);
+        put(name, inObject);
+        return this;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/ObjectInfo.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/ObjectInfo.java
new file mode 100644
index 0000000..ae548a6
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/ObjectInfo.java
@@ -0,0 +1,56 @@
+package org.keycloak.models.mongo.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.keycloak.models.mongo.api.NoSQLObject;
+import org.picketlink.common.properties.Property;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ObjectInfo {
+
+    private final Class<? extends NoSQLObject> objectClass;
+
+    private final String dbCollectionName;
+
+    private final Property<String> oidProperty;
+
+    private final Map<String, Property<Object>> properties;
+
+    public ObjectInfo(Class<? extends NoSQLObject> objectClass, String dbCollectionName, Property<String> oidProperty, List<Property<Object>> properties) {
+        this.objectClass = objectClass;
+        this.dbCollectionName = dbCollectionName;
+        this.oidProperty = oidProperty;
+
+        Map<String, Property<Object>> props= new HashMap<String, Property<Object>>();
+        for (Property<Object> property : properties) {
+            props.put(property.getName(), property);
+        }
+        this.properties = Collections.unmodifiableMap(props);
+    }
+
+    public Class<? extends NoSQLObject> getObjectClass() {
+        return objectClass;
+    }
+
+    public String getDbCollectionName() {
+        return dbCollectionName;
+    }
+
+    public Property<String> getOidProperty() {
+        return oidProperty;
+    }
+
+    public Collection<Property<Object>> getProperties() {
+        return properties.values();
+    }
+
+    public Property<Object> getPropertyByName(String propertyName) {
+        return properties.get(propertyName);
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java
new file mode 100644
index 0000000..896257f
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java
@@ -0,0 +1,73 @@
+package org.keycloak.models.mongo.impl.types;
+
+import java.util.ArrayList;
+
+import com.mongodb.BasicDBList;
+import com.mongodb.BasicDBObject;
+import org.keycloak.models.mongo.api.types.Converter;
+import org.keycloak.models.mongo.api.types.TypeConverter;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class BasicDBListConverter implements Converter<BasicDBList, ArrayList> {
+
+    private final TypeConverter typeConverter;
+
+    public BasicDBListConverter(TypeConverter typeConverter) {
+        this.typeConverter = typeConverter;
+    }
+
+    @Override
+    public ArrayList convertObject(BasicDBList dbList) {
+        ArrayList<Object> appObjects = new ArrayList<Object>();
+        Class<?> expectedListElementType = null;
+        for (Object dbObject : dbList) {
+
+            if (expectedListElementType == null) {
+                expectedListElementType = findExpectedListElementType(dbObject);
+            }
+
+            appObjects.add(typeConverter.convertDBObjectToApplicationObject(dbObject, expectedListElementType));
+        }
+        return appObjects;
+    }
+
+    @Override
+    public Class<? extends BasicDBList> getConverterObjectType() {
+        return BasicDBList.class;
+    }
+
+    @Override
+    public Class<ArrayList> getExpectedReturnType() {
+        return ArrayList.class;
+    }
+
+    private Class<?> findExpectedListElementType(Object dbObject) {
+        if (dbObject instanceof BasicDBObject) {
+            BasicDBObject basicDBObject = (BasicDBObject) dbObject;
+            String type = (String)basicDBObject.get(ListConverter.OBJECT_TYPE);
+            if (type == null) {
+                throw new IllegalStateException("Not found OBJECT_TYPE key inside object " + dbObject);
+            }
+            basicDBObject.remove(ListConverter.OBJECT_TYPE);
+
+            try {
+                return Class.forName(type);
+            } catch (ClassNotFoundException cnfe) {
+                throw new RuntimeException(cnfe);
+            }
+        } else {
+            // Special case (if we have String like "org.keycloak.Gender###MALE" we expect that substring before ### is className
+            if (String.class.equals(dbObject.getClass())) {
+                String dbObjString = (String)dbObject;
+                if (dbObjString.contains(ClassCache.SPLIT)) {
+                    String className = dbObjString.substring(0, dbObjString.indexOf(ClassCache.SPLIT));
+                    return ClassCache.getInstance().getOrLoadClass(className);
+                }
+            }
+
+            return dbObject.getClass();
+        }
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectConverter.java
new file mode 100644
index 0000000..a423652
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectConverter.java
@@ -0,0 +1,102 @@
+package org.keycloak.models.mongo.impl.types;
+
+import com.mongodb.BasicDBObject;
+import org.jboss.logging.Logger;
+import org.keycloak.models.mongo.api.AttributedNoSQLObject;
+import org.keycloak.models.mongo.api.NoSQLObject;
+import org.keycloak.models.mongo.api.types.Converter;
+import org.keycloak.models.mongo.api.types.TypeConverter;
+import org.keycloak.models.mongo.impl.MongoDBImpl;
+import org.keycloak.models.mongo.impl.ObjectInfo;
+import org.picketlink.common.properties.Property;
+import org.picketlink.common.reflection.Types;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class BasicDBObjectConverter<S extends NoSQLObject> implements Converter<BasicDBObject, S> {
+
+    private static final Logger logger = Logger.getLogger(BasicDBObjectConverter.class);
+
+    private final MongoDBImpl mongoDBImpl;
+    private final TypeConverter typeConverter;
+    private final Class<S> expectedNoSQLObjectType;
+
+    public BasicDBObjectConverter(MongoDBImpl mongoDBImpl, TypeConverter typeConverter, Class<S> expectedNoSQLObjectType) {
+        this.mongoDBImpl = mongoDBImpl;
+        this.typeConverter = typeConverter;
+        this.expectedNoSQLObjectType = expectedNoSQLObjectType;
+    }
+
+    @Override
+    public S convertObject(BasicDBObject dbObject) {
+        if (dbObject == null) {
+            return null;
+        }
+
+        ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(expectedNoSQLObjectType);
+
+        S object;
+        try {
+            object = expectedNoSQLObjectType.newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        for (String key : dbObject.keySet()) {
+            Object value = dbObject.get(key);
+            Property<Object> property;
+
+            if ("_id".equals(key)) {
+                // Current property is "id"
+                Property<String> idProperty = objectInfo.getOidProperty();
+                if (idProperty != null) {
+                    idProperty.setValue(object, value.toString());
+                }
+
+            } else if ((property = objectInfo.getPropertyByName(key)) != null) {
+                // It's declared property with @DBField annotation
+                setPropertyValue(object, value, property);
+
+            } else if (object instanceof AttributedNoSQLObject) {
+                // It's attributed object and property is not declared, so we will call setAttribute
+                ((AttributedNoSQLObject)object).setAttribute(key, value.toString());
+
+            } else {
+                // Show warning if it's unknown
+                logger.warn("Property with key " + key + " not known for type " + expectedNoSQLObjectType);
+            }
+        }
+
+        return object;
+    }
+
+    private void setPropertyValue(NoSQLObject object, Object valueFromDB, Property property) {
+        if (valueFromDB == null) {
+            property.setValue(object, null);
+            return;
+        }
+
+        Class<?> expectedReturnType = property.getJavaClass();
+        // handle primitives
+        expectedReturnType = Types.boxedClass(expectedReturnType);
+
+        Object appObject = typeConverter.convertDBObjectToApplicationObject(valueFromDB, expectedReturnType);
+        if (Types.boxedClass(property.getJavaClass()).isAssignableFrom(appObject.getClass())) {
+            property.setValue(object, appObject);
+        } else {
+            throw new IllegalStateException("Converted object " + appObject + " is not of type " +  expectedReturnType +
+                    ". So can't be assigned as property " + property.getName() + " of " + object.getClass());
+        }
+    }
+
+    @Override
+    public Class<? extends BasicDBObject> getConverterObjectType() {
+        return BasicDBObject.class;
+    }
+
+    @Override
+    public Class<S> getExpectedReturnType() {
+        return expectedNoSQLObjectType;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ClassCache.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ClassCache.java
new file mode 100644
index 0000000..891ccdd
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ClassCache.java
@@ -0,0 +1,37 @@
+package org.keycloak.models.mongo.impl.types;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Helper class for caching of classNames to actual classes (Should help a bit to avoid expensive reflection calls)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ClassCache {
+
+    public static final String SPLIT =  "###";
+    private static final ClassCache INSTANCE = new ClassCache();
+
+    private ConcurrentMap<String, Class<?>> cache = new ConcurrentHashMap<String, Class<?>>();
+
+    private ClassCache() {};
+
+    public static ClassCache getInstance() {
+        return INSTANCE;
+    }
+
+    public Class<?> getOrLoadClass(String className) {
+        Class<?> clazz = cache.get(className);
+        if (clazz == null) {
+            try {
+                clazz = Class.forName(className);
+                cache.putIfAbsent(className, clazz);
+            } catch (ClassNotFoundException cnfe) {
+                throw new RuntimeException(cnfe);
+            }
+        }
+        return clazz;
+    }
+
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringConverter.java
new file mode 100644
index 0000000..2a800df
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringConverter.java
@@ -0,0 +1,26 @@
+package org.keycloak.models.mongo.impl.types;
+
+import org.keycloak.models.mongo.api.types.Converter;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class EnumToStringConverter implements Converter<Enum, String> {
+
+    // It will be saved in form of "org.keycloak.Gender#MALE" so it's possible to parse enumType out of it
+    @Override
+    public String convertObject(Enum objectToConvert) {
+        String className = objectToConvert.getClass().getName();
+        return className + ClassCache.SPLIT + objectToConvert.toString();
+    }
+
+    @Override
+    public Class<? extends Enum> getConverterObjectType() {
+        return Enum.class;
+    }
+
+    @Override
+    public Class<String> getExpectedReturnType() {
+        return String.class;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java
new file mode 100644
index 0000000..8b72ca2
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java
@@ -0,0 +1,52 @@
+package org.keycloak.models.mongo.impl.types;
+
+import java.util.List;
+
+import com.mongodb.BasicDBList;
+import com.mongodb.BasicDBObject;
+import org.keycloak.models.mongo.api.types.Converter;
+import org.keycloak.models.mongo.api.types.TypeConverter;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ListConverter<T extends List> implements Converter<T, BasicDBList> {
+
+    // Key for ObjectType field, which points to actual Java type of element objects inside list
+    static final String OBJECT_TYPE = "OBJECT_TYPE";
+
+    private final TypeConverter typeConverter;
+    private final Class<T> listType;
+
+    public ListConverter(TypeConverter typeConverter, Class<T> listType) {
+        this.typeConverter = typeConverter;
+        this.listType = listType;
+    }
+
+    @Override
+    public BasicDBList convertObject(T appObjectsList) {
+        BasicDBList dbObjects = new BasicDBList();
+        for (Object appObject : appObjectsList) {
+            Object dbObject = typeConverter.convertApplicationObjectToDBObject(appObject, Object.class);
+
+            // We need to add OBJECT_TYPE key to object, so we can retrieve correct Java type of object during load of this list
+            if (dbObject instanceof BasicDBObject) {
+                BasicDBObject basicDBObject = (BasicDBObject)dbObject;
+                basicDBObject.put(OBJECT_TYPE, appObject.getClass().getName());
+            }
+
+            dbObjects.add(dbObject);
+        }
+        return dbObjects;
+    }
+
+    @Override
+    public Class<? extends T> getConverterObjectType() {
+        return listType;
+    }
+
+    @Override
+    public Class<BasicDBList> getExpectedReturnType() {
+        return BasicDBList.class;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/NoSQLObjectConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/NoSQLObjectConverter.java
new file mode 100644
index 0000000..f7be7ae
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/NoSQLObjectConverter.java
@@ -0,0 +1,66 @@
+package org.keycloak.models.mongo.impl.types;
+
+import java.util.Collection;
+import java.util.Map;
+
+import com.mongodb.BasicDBObject;
+import org.keycloak.models.mongo.api.AttributedNoSQLObject;
+import org.keycloak.models.mongo.api.NoSQLObject;
+import org.keycloak.models.mongo.api.types.Converter;
+import org.keycloak.models.mongo.api.types.TypeConverter;
+import org.keycloak.models.mongo.impl.MongoDBImpl;
+import org.keycloak.models.mongo.impl.ObjectInfo;
+import org.picketlink.common.properties.Property;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class NoSQLObjectConverter<T extends NoSQLObject> implements Converter<T, BasicDBObject> {
+
+    private final MongoDBImpl mongoDBImpl;
+    private final TypeConverter typeConverter;
+    private final Class<T> expectedNoSQLObjectType;
+
+    public NoSQLObjectConverter(MongoDBImpl mongoDBImpl, TypeConverter typeConverter, Class<T> expectedNoSQLObjectType) {
+        this.mongoDBImpl = mongoDBImpl;
+        this.typeConverter = typeConverter;
+        this.expectedNoSQLObjectType = expectedNoSQLObjectType;
+    }
+
+    @Override
+    public BasicDBObject convertObject(T applicationObject) {
+        ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(applicationObject.getClass());
+
+        // Create instance of BasicDBObject and add all declared properties to it (properties with null value probably should be skipped)
+        BasicDBObject dbObject = new BasicDBObject();
+        Collection<Property<Object>> props = objectInfo.getProperties();
+        for (Property<Object> property : props) {
+            String propName = property.getName();
+            Object propValue = property.getValue(applicationObject);
+
+            Object dbValue = propValue == null ? null : typeConverter.convertApplicationObjectToDBObject(propValue, Object.class);
+            dbObject.put(propName, dbValue);
+        }
+
+        // Adding attributes
+        if (applicationObject instanceof AttributedNoSQLObject) {
+            AttributedNoSQLObject attributedObject = (AttributedNoSQLObject)applicationObject;
+            Map<String, String> attributes = attributedObject.getAttributes();
+            for (Map.Entry<String, String> attribute : attributes.entrySet()) {
+                dbObject.append(attribute.getKey(), attribute.getValue());
+            }
+        }
+
+        return dbObject;
+    }
+
+    @Override
+    public Class<? extends T> getConverterObjectType() {
+        return expectedNoSQLObjectType;
+    }
+
+    @Override
+    public Class<BasicDBObject> getExpectedReturnType() {
+        return BasicDBObject.class;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleConverter.java
new file mode 100644
index 0000000..5ba1de5
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleConverter.java
@@ -0,0 +1,30 @@
+package org.keycloak.models.mongo.impl.types;
+
+import org.keycloak.models.mongo.api.types.Converter;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SimpleConverter<T> implements Converter<T, T> {
+
+    private final Class<T> expectedType;
+
+    public SimpleConverter(Class<T> expectedType) {
+        this.expectedType = expectedType;
+    }
+
+    @Override
+    public T convertObject(T objectToConvert) {
+        return objectToConvert;
+    }
+
+    @Override
+    public Class<? extends T> getConverterObjectType() {
+        return expectedType;
+    }
+
+    @Override
+    public Class<T> getExpectedReturnType() {
+        return expectedType;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java
new file mode 100644
index 0000000..0c948ec
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java
@@ -0,0 +1,32 @@
+package org.keycloak.models.mongo.impl.types;
+
+import org.keycloak.models.mongo.api.types.Converter;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class StringToEnumConverter implements Converter<String, Enum> {
+
+    @Override
+    public Enum convertObject(String objectToConvert) {
+        int index = objectToConvert.indexOf(ClassCache.SPLIT);
+        if (index == -1) {
+            throw new IllegalStateException("Can't convert enum type with value " + objectToConvert);
+        }
+
+        String className = objectToConvert.substring(0, index);
+        String enumValue = objectToConvert.substring(index + 3);
+        Class<? extends Enum> clazz = (Class<? extends Enum>)ClassCache.getInstance().getOrLoadClass(className);
+        return Enum.valueOf(clazz, enumValue);
+    }
+
+    @Override
+    public Class<? extends String> getConverterObjectType() {
+        return String.class;
+    }
+
+    @Override
+    public Class<Enum> getExpectedReturnType() {
+        return Enum.class;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
new file mode 100644
index 0000000..49bcd31
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
@@ -0,0 +1,284 @@
+package org.keycloak.models.mongo.keycloak.adapters;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+import org.keycloak.models.mongo.keycloak.data.ApplicationData;
+import org.keycloak.models.mongo.keycloak.data.RoleData;
+import org.keycloak.models.mongo.keycloak.data.UserData;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ApplicationAdapter implements ApplicationModel {
+
+    private final ApplicationData application;
+    private final NoSQL noSQL;
+
+    private UserData resourceUser;
+
+    public ApplicationAdapter(ApplicationData applicationData, NoSQL noSQL) {
+        this.application = applicationData;
+        this.noSQL = noSQL;
+    }
+
+    @Override
+    public void updateApplication() {
+        noSQL.saveObject(application);
+    }
+
+    @Override
+    public UserModel getApplicationUser() {
+        // This is not thread-safe. Assumption is that ApplicationAdapter instance is per-client object
+        if (resourceUser == null) {
+            resourceUser = noSQL.loadObject(UserData.class, application.getResourceUserId());
+        }
+
+        return resourceUser != null ? new UserAdapter(resourceUser, noSQL) : null;
+    }
+
+    @Override
+    public String getId() {
+        return application.getId();
+    }
+
+    @Override
+    public String getName() {
+        return application.getName();
+    }
+
+    @Override
+    public void setName(String name) {
+        application.setName(name);
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return application.isEnabled();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        application.setEnabled(enabled);
+    }
+
+    @Override
+    public boolean isSurrogateAuthRequired() {
+        return application.isSurrogateAuthRequired();
+    }
+
+    @Override
+    public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
+        application.setSurrogateAuthRequired(surrogateAuthRequired);
+    }
+
+    @Override
+    public String getManagementUrl() {
+        return application.getManagementUrl();
+    }
+
+    @Override
+    public void setManagementUrl(String url) {
+        application.setManagementUrl(url);
+    }
+
+    @Override
+    public void setBaseUrl(String url) {
+        application.setBaseUrl(url);
+    }
+
+    @Override
+    public String getBaseUrl() {
+        return application.getBaseUrl();
+    }
+
+    @Override
+    public RoleAdapter getRole(String name) {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("name", name)
+                .andCondition("applicationId", getId())
+                .build();
+        RoleData role = noSQL.loadSingleObject(RoleData.class, query);
+        if (role == null) {
+            return null;
+        } else {
+            return new RoleAdapter(role, noSQL);
+        }
+    }
+
+    @Override
+    public RoleModel getRoleById(String id) {
+        RoleData role = noSQL.loadObject(RoleData.class, id);
+        if (role == null) {
+            return null;
+        } else {
+            return new RoleAdapter(role, noSQL);
+        }
+    }
+
+    @Override
+    public void grantRole(UserModel user, RoleModel role) {
+        UserData userData = ((UserAdapter)user).getUser();
+        noSQL.pushItemToList(userData, "roleIds", role.getId());
+    }
+
+    @Override
+    public boolean hasRole(UserModel user, String role) {
+        RoleModel roleModel = getRole(role);
+        return hasRole(user, roleModel);
+    }
+
+    @Override
+    public boolean hasRole(UserModel user, RoleModel role) {
+        UserData userData = ((UserAdapter)user).getUser();
+
+        List<String> roleIds = userData.getRoleIds();
+        String roleId = role.getId();
+        if (roleIds != null) {
+            for (String currentId : roleIds) {
+                if (roleId.equals(currentId)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public RoleAdapter addRole(String name) {
+        if (getRole(name) != null) {
+            throw new IllegalArgumentException("Role " + name + " already exists");
+        }
+
+        RoleData roleData = new RoleData();
+        roleData.setName(name);
+        roleData.setApplicationId(getId());
+
+        noSQL.saveObject(roleData);
+        return new RoleAdapter(roleData, noSQL);
+    }
+
+    @Override
+    public List<RoleModel> getRoles() {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("applicationId", getId())
+                .build();
+        List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
+
+        List<RoleModel> result = new ArrayList<RoleModel>();
+        for (RoleData role : roles) {
+            result.add(new RoleAdapter(role, noSQL));
+        }
+
+        return result;
+    }
+
+    // Static so that it can be used from RealmAdapter as well
+    static List<RoleData> getAllRolesOfUser(UserModel user, NoSQL noSQL) {
+        UserData userData = ((UserAdapter)user).getUser();
+        List<String> roleIds = userData.getRoleIds();
+
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .inCondition("_id", roleIds)
+                .build();
+        return noSQL.loadObjects(RoleData.class, query);
+    }
+
+    @Override
+    public Set<String> getRoleMappingValues(UserModel user) {
+        Set<String> result = new HashSet<String>();
+        List<RoleData> roles = getAllRolesOfUser(user, noSQL);
+        // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
+        for (RoleData role : roles) {
+            if (getId().equals(role.getApplicationId())) {
+                result.add(role.getName());
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public List<RoleModel> getRoleMappings(UserModel user) {
+        List<RoleModel> result = new ArrayList<RoleModel>();
+        List<RoleData> roles = getAllRolesOfUser(user, noSQL);
+        // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
+        for (RoleData role : roles) {
+            if (getId().equals(role.getApplicationId())) {
+                result.add(new RoleAdapter(role, noSQL));
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public void deleteRoleMapping(UserModel user, RoleModel role) {
+        UserData userData = ((UserAdapter)user).getUser();
+        noSQL.pullItemFromList(userData, "roleIds", role.getId());
+    }
+
+    @Override
+    public void addScopeMapping(UserModel agent, String roleName) {
+        RoleAdapter role = getRole(roleName);
+        if (role == null) {
+            throw new RuntimeException("Role not found");
+        }
+
+        addScopeMapping(agent, role);
+    }
+
+    @Override
+    public void addScopeMapping(UserModel agent, RoleModel role) {
+        UserData userData = ((UserAdapter)agent).getUser();
+        noSQL.pushItemToList(userData, "scopeIds", role.getId());
+    }
+
+    @Override
+    public void deleteScopeMapping(UserModel user, RoleModel role) {
+        UserData userData = ((UserAdapter)user).getUser();
+        noSQL.pullItemFromList(userData, "scopeIds", role.getId());
+    }
+
+    // Static so that it can be used from RealmAdapter as well
+    static List<RoleData> getAllScopesOfUser(UserModel user, NoSQL noSQL) {
+        UserData userData = ((UserAdapter)user).getUser();
+        List<String> roleIds = userData.getScopeIds();
+
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .inCondition("_id", roleIds)
+                .build();
+        return noSQL.loadObjects(RoleData.class, query);
+    }
+
+    @Override
+    public Set<String> getScopeMappingValues(UserModel agent) {
+        Set<String> result = new HashSet<String>();
+        List<RoleData> roles = getAllScopesOfUser(agent,  noSQL);
+        // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
+        for (RoleData role : roles) {
+            if (getId().equals(role.getApplicationId())) {
+                result.add(role.getName());
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public List<RoleModel> getScopeMappings(UserModel agent) {
+        List<RoleModel> result = new ArrayList<RoleModel>();
+        List<RoleData> roles = getAllScopesOfUser(agent,  noSQL);
+        // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
+        for (RoleData role : roles) {
+            if (getId().equals(role.getApplicationId())) {
+                result.add(new RoleAdapter(role, noSQL));
+            }
+        }
+        return result;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoDBSessionFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoDBSessionFactory.java
new file mode 100644
index 0000000..b1ac509
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoDBSessionFactory.java
@@ -0,0 +1,70 @@
+package org.keycloak.models.mongo.keycloak.adapters;
+
+import java.net.UnknownHostException;
+
+import com.mongodb.DB;
+import com.mongodb.MongoClient;
+import org.jboss.logging.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.NoSQLObject;
+import org.keycloak.models.mongo.keycloak.data.ApplicationData;
+import org.keycloak.models.mongo.keycloak.data.OAuthClientData;
+import org.keycloak.models.mongo.keycloak.data.RealmData;
+import org.keycloak.models.mongo.keycloak.data.RequiredCredentialData;
+import org.keycloak.models.mongo.keycloak.data.RoleData;
+import org.keycloak.models.mongo.keycloak.data.SocialLinkData;
+import org.keycloak.models.mongo.keycloak.data.UserData;
+import org.keycloak.models.mongo.impl.MongoDBImpl;
+import org.keycloak.models.mongo.keycloak.data.credentials.OTPData;
+import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData;
+
+/**
+ * NoSQL implementation based on MongoDB
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MongoDBSessionFactory implements KeycloakSessionFactory {
+    protected static final Logger logger = Logger.getLogger(MongoDBSessionFactory.class);
+
+    private static final Class<? extends NoSQLObject>[] MANAGED_DATA_TYPES = (Class<? extends NoSQLObject>[])new Class<?>[] {
+            RealmData.class,
+            UserData.class,
+            RoleData.class,
+            RequiredCredentialData.class,
+            PasswordData.class,
+            OTPData.class,
+            SocialLinkData.class,
+            ApplicationData.class,
+            OAuthClientData.class
+    };
+
+    private final MongoClient mongoClient;
+    private final NoSQL mongoDB;
+
+    public MongoDBSessionFactory(String host, int port, String dbName, boolean dropDatabaseOnStartup) {
+        logger.info(String.format("Going to use MongoDB database. host: %s, port: %d, databaseName: %s, removeAllObjectsAtStartup: %b", host, port, dbName, dropDatabaseOnStartup));
+        try {
+            // TODO: authentication support
+            mongoClient = new MongoClient(host, port);
+
+            DB db = mongoClient.getDB(dbName);
+            mongoDB = new MongoDBImpl(db, dropDatabaseOnStartup, MANAGED_DATA_TYPES);
+
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public KeycloakSession createSession() {
+        return new NoSQLSession(mongoDB);
+    }
+
+    @Override
+    public void close() {
+        logger.info("Closing MongoDB client");
+        mongoClient.close();
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLSession.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLSession.java
new file mode 100644
index 0000000..2bc413d
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLSession.java
@@ -0,0 +1,86 @@
+package org.keycloak.models.mongo.keycloak.adapters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+import org.keycloak.models.mongo.keycloak.data.RealmData;
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.utils.KeycloakSessionUtils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class NoSQLSession implements KeycloakSession {
+
+    private static final NoSQLTransaction PLACEHOLDER = new NoSQLTransaction();
+    private final NoSQL noSQL;
+
+    public NoSQLSession(NoSQL noSQL) {
+        this.noSQL = noSQL;
+    }
+
+    @Override
+    public KeycloakTransaction getTransaction() {
+        return PLACEHOLDER;
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public RealmModel createRealm(String name) {
+        return createRealm(KeycloakSessionUtils.generateId(), name);
+    }
+
+    @Override
+    public RealmModel createRealm(String id, String name) {
+        if (getRealm(id) != null) {
+            throw new IllegalStateException("Realm with id '" + id + "' already exists");
+        }
+
+        RealmData newRealm = new RealmData();
+        newRealm.setId(id);
+        newRealm.setName(name);
+
+        noSQL.saveObject(newRealm);
+
+        RealmAdapter realm = new RealmAdapter(newRealm, noSQL);
+        return realm;
+    }
+
+    @Override
+    public RealmModel getRealm(String id) {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("id", id)
+                .build();
+        RealmData realmData = noSQL.loadSingleObject(RealmData.class, query);
+        return realmData != null ? new RealmAdapter(realmData, noSQL) : null;
+    }
+
+    @Override
+    public List<RealmModel> getRealms(UserModel admin) {
+        String userId = ((UserAdapter)admin).getUser().getId();
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("realmAdmins", userId)
+                .build();
+        List<RealmData> realms = noSQL.loadObjects(RealmData.class, query);
+
+        List<RealmModel> results = new ArrayList<RealmModel>();
+        for (RealmData realmData : realms) {
+            results.add(new RealmAdapter(realmData, noSQL));
+        }
+        return results;
+    }
+
+    @Override
+    public void deleteRealm(RealmModel realm) {
+        String oid = ((RealmAdapter)realm).getOid();
+        noSQL.removeObject(RealmData.class, oid);
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLTransaction.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLTransaction.java
new file mode 100644
index 0000000..3d16635
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLTransaction.java
@@ -0,0 +1,39 @@
+package org.keycloak.models.mongo.keycloak.adapters;
+
+import org.keycloak.models.KeycloakTransaction;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class NoSQLTransaction implements KeycloakTransaction {
+
+    @Override
+    public void begin() {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void commit() {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void rollback() {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void setRollbackOnly() {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public boolean getRollbackOnly() {
+        return false;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public boolean isActive() {
+        return true;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java
new file mode 100644
index 0000000..34f455e
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java
@@ -0,0 +1,54 @@
+package org.keycloak.models.mongo.keycloak.adapters;
+
+import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.keycloak.data.OAuthClientData;
+import org.keycloak.models.mongo.keycloak.data.UserData;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OAuthClientAdapter implements OAuthClientModel {
+
+    private final OAuthClientData delegate;
+    private UserAdapter oauthAgent;
+    private final NoSQL noSQL;
+
+    public OAuthClientAdapter(OAuthClientData oauthClientData, UserAdapter oauthAgent, NoSQL noSQL) {
+        this.delegate = oauthClientData;
+        this.oauthAgent = oauthAgent;
+        this.noSQL = noSQL;
+    }
+
+    public OAuthClientAdapter(OAuthClientData oauthClientData, NoSQL noSQL) {
+        this.delegate = oauthClientData;
+        this.noSQL = noSQL;
+    }
+
+    @Override
+    public String getId() {
+        return delegate.getId();
+    }
+
+    @Override
+    public UserModel getOAuthAgent() {
+        // This is not thread-safe. Assumption is that OAuthClientAdapter instance is per-client object
+        if (oauthAgent == null) {
+            UserData user = noSQL.loadObject(UserData.class, delegate.getOauthAgentId());
+            oauthAgent = user!=null ? new UserAdapter(user, noSQL) : null;
+        }
+        return oauthAgent;
+    }
+
+    @Override
+    public String getBaseUrl() {
+        return delegate.getBaseUrl();
+    }
+
+    @Override
+    public void setBaseUrl(String base) {
+        delegate.setBaseUrl(base);
+        noSQL.saveObject(delegate);
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
new file mode 100644
index 0000000..837f985
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -0,0 +1,862 @@
+package org.keycloak.models.mongo.keycloak.adapters;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.openssl.PEMWriter;
+import org.keycloak.PemUtils;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.SocialLinkModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
+import org.keycloak.models.mongo.keycloak.data.OAuthClientData;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+import org.keycloak.models.mongo.keycloak.credentials.PasswordCredentialHandler;
+import org.keycloak.models.mongo.keycloak.credentials.TOTPCredentialHandler;
+import org.keycloak.models.mongo.keycloak.data.ApplicationData;
+import org.keycloak.models.mongo.keycloak.data.RealmData;
+import org.keycloak.models.mongo.keycloak.data.RequiredCredentialData;
+import org.keycloak.models.mongo.keycloak.data.RoleData;
+import org.keycloak.models.mongo.keycloak.data.SocialLinkData;
+import org.keycloak.models.mongo.keycloak.data.UserData;
+import org.picketlink.idm.credential.Credentials;
+import org.picketlink.idm.model.sample.User;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RealmAdapter implements RealmModel {
+
+    private final RealmData realm;
+    private final NoSQL noSQL;
+
+    protected volatile transient PublicKey publicKey;
+    protected volatile transient PrivateKey privateKey;
+
+    // TODO: likely shouldn't be static. And ATM, just empty map is passed -> It's not possible to configure stuff like PasswordEncoder etc.
+    private static PasswordCredentialHandler passwordCredentialHandler = new PasswordCredentialHandler(new HashMap<String, Object>());
+    private static TOTPCredentialHandler totpCredentialHandler = new TOTPCredentialHandler(new HashMap<String, Object>());
+
+    public RealmAdapter(RealmData realmData, NoSQL noSQL) {
+        this.realm = realmData;
+        this.noSQL = noSQL;
+    }
+
+    protected String getOid() {
+        return realm.getOid();
+    }
+
+    @Override
+    public String getId() {
+        return realm.getId();
+    }
+
+    @Override
+    public String getName() {
+        return realm.getName();
+    }
+
+    @Override
+    public void setName(String name) {
+        realm.setName(name);
+        updateRealm();
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return realm.isEnabled();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        realm.setEnabled(enabled);
+        updateRealm();
+    }
+
+    @Override
+    public boolean isSocial() {
+        return realm.isSocial();
+    }
+
+    @Override
+    public void setSocial(boolean social) {
+        realm.setSocial(social);
+        updateRealm();
+    }
+
+    @Override
+    public boolean isAutomaticRegistrationAfterSocialLogin() {
+        return realm.isAutomaticRegistrationAfterSocialLogin();
+    }
+
+    @Override
+    public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
+        realm.setAutomaticRegistrationAfterSocialLogin(automaticRegistrationAfterSocialLogin);
+        updateRealm();
+    }
+
+    @Override
+    public boolean isSslNotRequired() {
+        return realm.isSslNotRequired();
+    }
+
+    @Override
+    public void setSslNotRequired(boolean sslNotRequired) {
+        realm.setSslNotRequired(sslNotRequired);
+        updateRealm();
+    }
+
+    @Override
+    public boolean isCookieLoginAllowed() {
+        return realm.isCookieLoginAllowed();
+    }
+
+    @Override
+    public void setCookieLoginAllowed(boolean cookieLoginAllowed) {
+        realm.setCookieLoginAllowed(cookieLoginAllowed);
+        updateRealm();
+    }
+
+    @Override
+    public boolean isRegistrationAllowed() {
+        return realm.isRegistrationAllowed();
+    }
+
+    @Override
+    public void setRegistrationAllowed(boolean registrationAllowed) {
+        realm.setRegistrationAllowed(registrationAllowed);
+        updateRealm();
+    }
+
+    @Override
+    public boolean isVerifyEmail() {
+        return realm.isVerifyEmail();
+    }
+
+    @Override
+    public void setVerifyEmail(boolean verifyEmail) {
+        realm.setVerifyEmail(verifyEmail);
+        updateRealm();
+    }
+
+    @Override
+    public boolean isResetPasswordAllowed() {
+        return realm.isResetPasswordAllowed();
+    }
+
+    @Override
+    public void setResetPasswordAllowed(boolean resetPassword) {
+        realm.setResetPasswordAllowed(resetPassword);
+        updateRealm();
+    }
+
+    @Override
+    public int getTokenLifespan() {
+        return realm.getTokenLifespan();
+    }
+
+    @Override
+    public void setTokenLifespan(int tokenLifespan) {
+        realm.setTokenLifespan(tokenLifespan);
+        updateRealm();
+    }
+
+    @Override
+    public int getAccessCodeLifespan() {
+        return realm.getAccessCodeLifespan();
+    }
+
+    @Override
+    public void setAccessCodeLifespan(int accessCodeLifespan) {
+        realm.setAccessCodeLifespan(accessCodeLifespan);
+        updateRealm();
+    }
+
+    @Override
+    public int getAccessCodeLifespanUserAction() {
+        return realm.getAccessCodeLifespanUserAction();
+    }
+
+    @Override
+    public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
+        realm.setAccessCodeLifespanUserAction(accessCodeLifespanUserAction);
+        updateRealm();
+    }
+
+    @Override
+    public String getPublicKeyPem() {
+        return realm.getPublicKeyPem();
+    }
+
+    @Override
+    public void setPublicKeyPem(String publicKeyPem) {
+        realm.setPublicKeyPem(publicKeyPem);
+        this.publicKey = null;
+        updateRealm();
+    }
+
+    @Override
+    public String getPrivateKeyPem() {
+        return realm.getPrivateKeyPem();
+    }
+
+    @Override
+    public void setPrivateKeyPem(String privateKeyPem) {
+        realm.setPrivateKeyPem(privateKeyPem);
+        this.privateKey = null;
+        updateRealm();
+    }
+
+    @Override
+    public PublicKey getPublicKey() {
+        if (publicKey != null) return publicKey;
+        String pem = getPublicKeyPem();
+        if (pem != null) {
+            try {
+                publicKey = PemUtils.decodePublicKey(pem);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return publicKey;
+    }
+
+    @Override
+    public void setPublicKey(PublicKey publicKey) {
+        this.publicKey = publicKey;
+        StringWriter writer = new StringWriter();
+        PEMWriter pemWriter = new PEMWriter(writer);
+        try {
+            pemWriter.writeObject(publicKey);
+            pemWriter.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        String s = writer.toString();
+        setPublicKeyPem(PemUtils.removeBeginEnd(s));
+    }
+
+    @Override
+    public PrivateKey getPrivateKey() {
+        if (privateKey != null) return privateKey;
+        String pem = getPrivateKeyPem();
+        if (pem != null) {
+            try {
+                privateKey = PemUtils.decodePrivateKey(pem);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return privateKey;
+    }
+
+    @Override
+    public void setPrivateKey(PrivateKey privateKey) {
+        this.privateKey = privateKey;
+        StringWriter writer = new StringWriter();
+        PEMWriter pemWriter = new PEMWriter(writer);
+        try {
+            pemWriter.writeObject(privateKey);
+            pemWriter.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        String s = writer.toString();
+        setPrivateKeyPem(PemUtils.removeBeginEnd(s));
+    }
+
+    @Override
+    public UserAdapter getUser(String name) {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("loginName", name)
+                .andCondition("realmId", getOid())
+                .build();
+        UserData user = noSQL.loadSingleObject(UserData.class, query);
+
+        if (user == null) {
+            return null;
+        } else {
+            return new UserAdapter(user, noSQL);
+        }
+    }
+
+    @Override
+    public UserAdapter addUser(String username) {
+        if (getUser(username) != null) {
+            throw new IllegalArgumentException("User " + username + " already exists");
+        }
+
+        UserData userData = new UserData();
+        userData.setLoginName(username);
+        userData.setEnabled(true);
+        userData.setRealmId(getOid());
+
+        noSQL.saveObject(userData);
+        return new UserAdapter(userData, noSQL);
+    }
+
+    // This method doesn't exists on interface actually
+    public void removeUser(String name) {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("loginName", name)
+                .andCondition("realmId", getOid())
+                .build();
+        noSQL.removeObjects(UserData.class, query);
+    }
+
+    @Override
+    public RoleAdapter getRole(String name) {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("name", name)
+                .andCondition("realmId", getOid())
+                .build();
+        RoleData role = noSQL.loadSingleObject(RoleData.class, query);
+        if (role == null) {
+            return null;
+        } else {
+            return new RoleAdapter(role, noSQL);
+        }
+    }
+
+    @Override
+    public RoleModel addRole(String name) {
+        if (getRole(name) != null) {
+            throw new IllegalArgumentException("Role " + name + " already exists");
+        }
+
+        RoleData roleData = new RoleData();
+        roleData.setName(name);
+        roleData.setRealmId(getOid());
+
+        noSQL.saveObject(roleData);
+        return new RoleAdapter(roleData, noSQL);
+    }
+
+    @Override
+    public List<RoleModel> getRoles() {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("realmId", getOid())
+                .build();
+        List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
+
+        List<RoleModel> result = new ArrayList<RoleModel>();
+        for (RoleData role : roles) {
+            result.add(new RoleAdapter(role, noSQL));
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<RoleModel> getDefaultRoles() {
+        List<String> defaultRoles = realm.getDefaultRoles();
+
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .inCondition("_id", defaultRoles)
+                .build();
+        List<RoleData> defaultRolesData = noSQL.loadObjects(RoleData.class, query);
+
+        List<RoleModel> defaultRoleModels = new ArrayList<RoleModel>();
+        for (RoleData roleData : defaultRolesData) {
+            defaultRoleModels.add(new RoleAdapter(roleData, noSQL));
+        }
+        return defaultRoleModels;
+    }
+
+    @Override
+    public void addDefaultRole(String name) {
+        RoleModel role = getRole(name);
+        if (role == null) {
+            role = addRole(name);
+        }
+
+        noSQL.pushItemToList(realm, "defaultRoles", role.getId());
+    }
+
+    @Override
+    public void updateDefaultRoles(String[] defaultRoles) {
+        // defaultRoles is array with names of roles. So we need to convert to array of ids
+        List<String> roleIds = new ArrayList<String>();
+        for (String roleName : defaultRoles) {
+            RoleModel role = getRole(roleName);
+            if (role == null) {
+                role = addRole(roleName);
+            }
+
+            roleIds.add(role.getId());
+        }
+
+        realm.setDefaultRoles(roleIds);
+        updateRealm();
+    }
+
+    @Override
+    public ApplicationModel getApplicationById(String id) {
+        ApplicationData appData = noSQL.loadObject(ApplicationData.class, id);
+
+        // Check if application belongs to this realm
+        if (appData == null || !getOid().equals(appData.getRealmId())) {
+            return null;
+        }
+
+        ApplicationModel model = new ApplicationAdapter(appData, noSQL);
+        return model;
+    }
+
+    @Override
+    public Map<String, ApplicationModel> getApplicationNameMap() {
+        Map<String, ApplicationModel> resourceMap = new HashMap<String, ApplicationModel>();
+        for (ApplicationModel resource : getApplications()) {
+            resourceMap.put(resource.getName(), resource);
+        }
+        return resourceMap;
+    }
+
+    @Override
+    public List<ApplicationModel> getApplications() {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("realmId", getOid())
+                .build();
+        List<ApplicationData> appDatas = noSQL.loadObjects(ApplicationData.class, query);
+
+        List<ApplicationModel> result = new ArrayList<ApplicationModel>();
+        for (ApplicationData appData : appDatas) {
+            result.add(new ApplicationAdapter(appData, noSQL));
+        }
+        return result;
+    }
+
+    @Override
+    public ApplicationModel addApplication(String name) {
+        UserAdapter resourceUser = addUser(name);
+
+        ApplicationData appData = new ApplicationData();
+        appData.setName(name);
+        appData.setRealmId(getOid());
+        appData.setResourceUserId(resourceUser.getUser().getId());
+        noSQL.saveObject(appData);
+
+        ApplicationModel resource = new ApplicationAdapter(appData, noSQL);
+        resource.addRole("*");
+        resource.addScopeMapping(resourceUser, "*");
+        return resource;
+    }
+
+    @Override
+    public boolean hasRole(UserModel user, RoleModel role) {
+        UserData userData = ((UserAdapter)user).getUser();
+
+        List<String> roleIds = userData.getRoleIds();
+        String roleId = role.getId();
+        if (roleIds != null) {
+            for (String currentId : roleIds) {
+                if (roleId.equals(currentId)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void grantRole(UserModel user, RoleModel role) {
+        UserData userData = ((UserAdapter)user).getUser();
+        noSQL.pushItemToList(userData, "roleIds", role.getId());
+    }
+
+    @Override
+    public List<RoleModel> getRoleMappings(UserModel user) {
+        List<RoleModel> result = new ArrayList<RoleModel>();
+        List<RoleData> roles = ApplicationAdapter.getAllRolesOfUser(user, noSQL);
+        // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
+        for (RoleData role : roles) {
+            if (getOid().equals(role.getRealmId())) {
+                result.add(new RoleAdapter(role, noSQL));
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Set<String> getRoleMappingValues(UserModel user) {
+        Set<String> result = new HashSet<String>();
+        List<RoleData> roles = ApplicationAdapter.getAllRolesOfUser(user, noSQL);
+        // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
+        for (RoleData role : roles) {
+            if (getOid().equals(role.getRealmId())) {
+                result.add(role.getName());
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public void deleteRoleMapping(UserModel user, RoleModel role) {
+        UserData userData = ((UserAdapter)user).getUser();
+        noSQL.pullItemFromList(userData, "roleIds", role.getId());
+    }
+
+    @Override
+    public void addScopeMapping(UserModel agent, String roleName) {
+        RoleAdapter role = getRole(roleName);
+        if (role == null) {
+            throw new RuntimeException("Role not found");
+        }
+
+        addScopeMapping(agent, role);
+    }
+
+    @Override
+    public void addScopeMapping(UserModel agent, RoleModel role) {
+        UserData userData = ((UserAdapter)agent).getUser();
+        noSQL.pushItemToList(userData, "scopeIds", role.getId());
+    }
+
+    @Override
+    public void deleteScopeMapping(UserModel user, RoleModel role) {
+        UserData userData = ((UserAdapter)user).getUser();
+        noSQL.pullItemFromList(userData, "scopeIds", role.getId());
+    }
+
+    @Override
+    public OAuthClientModel addOAuthClient(String name) {
+        UserAdapter oauthAgent = addUser(name);
+
+        OAuthClientData oauthClient = new OAuthClientData();
+        oauthClient.setOauthAgentId(oauthAgent.getUser().getId());
+        oauthClient.setRealmId(getOid());
+        noSQL.saveObject(oauthClient);
+
+        return new OAuthClientAdapter(oauthClient, oauthAgent, noSQL);
+    }
+
+    @Override
+    public OAuthClientModel getOAuthClient(String name) {
+        UserAdapter user = getUser(name);
+        if (user == null) return null;
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("realmId", getOid())
+                .andCondition("oauthAgentId", user.getUser().getId())
+                .build();
+        OAuthClientData oauthClient = noSQL.loadSingleObject(OAuthClientData.class, query);
+        return oauthClient == null ? null : new OAuthClientAdapter(oauthClient, user, noSQL);
+    }
+
+    @Override
+    public List<OAuthClientModel> getOAuthClients() {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("realmId", getOid())
+                .build();
+        List<OAuthClientData> results = noSQL.loadObjects(OAuthClientData.class, query);
+        List<OAuthClientModel> list = new ArrayList<OAuthClientModel>();
+        for (OAuthClientData data : results) {
+            list.add(new OAuthClientAdapter(data, noSQL));
+        }
+        return list;
+    }
+
+    @Override
+    public List<RoleModel> getScopeMappings(UserModel agent) {
+        List<RoleModel> result = new ArrayList<RoleModel>();
+        List<RoleData> roles = ApplicationAdapter.getAllScopesOfUser(agent, noSQL);
+        // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
+        for (RoleData role : roles) {
+            if (getOid().equals(role.getRealmId())) {
+                result.add(new RoleAdapter(role, noSQL));
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Set<String> getScopeMappingValues(UserModel agent) {
+        Set<String> result = new HashSet<String>();
+        List<RoleData> roles = ApplicationAdapter.getAllScopesOfUser(agent, noSQL);
+        // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
+        for (RoleData role : roles) {
+            if (getOid().equals(role.getRealmId())) {
+                result.add(role.getName());
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public boolean isRealmAdmin(UserModel agent) {
+        List<String> realmAdmins = realm.getRealmAdmins();
+        String userId = ((UserAdapter)agent).getUser().getId();
+        return realmAdmins.contains(userId);
+    }
+
+    @Override
+    public void addRealmAdmin(UserModel agent) {
+        UserData userData = ((UserAdapter)agent).getUser();
+
+        noSQL.pushItemToList(realm, "realmAdmins", userData.getId());
+    }
+
+    @Override
+    public RoleModel getRoleById(String id) {
+        RoleData role = noSQL.loadObject(RoleData.class, id);
+        if (role == null) {
+            return null;
+        } else {
+            return new RoleAdapter(role, noSQL);
+        }
+    }
+
+    @Override
+    public boolean hasRole(UserModel user, String role) {
+        RoleModel roleModel = getRole(role);
+        return hasRole(user, roleModel);
+    }
+
+    @Override
+    public void addRequiredCredential(String cred) {
+        RequiredCredentialModel credentialModel = initRequiredCredentialModel(cred);
+        addRequiredCredential(credentialModel, RequiredCredentialData.CLIENT_TYPE_USER);
+    }
+
+    @Override
+    public void addRequiredResourceCredential(String type) {
+        RequiredCredentialModel credentialModel = initRequiredCredentialModel(type);
+        addRequiredCredential(credentialModel, RequiredCredentialData.CLIENT_TYPE_RESOURCE);
+    }
+
+    @Override
+    public void addRequiredOAuthClientCredential(String type) {
+        RequiredCredentialModel credentialModel = initRequiredCredentialModel(type);
+        addRequiredCredential(credentialModel, RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE);
+    }
+
+    protected void addRequiredCredential(RequiredCredentialModel credentialModel, int clientType) {
+        RequiredCredentialData credData = new RequiredCredentialData();
+        credData.setType(credentialModel.getType());
+        credData.setFormLabel(credentialModel.getFormLabel());
+        credData.setInput(credentialModel.isInput());
+        credData.setSecret(credentialModel.isSecret());
+
+        credData.setRealmId(getOid());
+        credData.setClientType(clientType);
+
+        noSQL.saveObject(credData);
+    }
+
+    @Override
+    public void updateRequiredCredentials(Set<String> creds) {
+        List<RequiredCredentialData> credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_USER);
+        updateRequiredCredentials(creds, credsData);
+    }
+
+    @Override
+    public void updateRequiredApplicationCredentials(Set<String> creds) {
+        List<RequiredCredentialData> credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_RESOURCE);
+        updateRequiredCredentials(creds, credsData);
+    }
+
+    @Override
+    public void updateRequiredOAuthClientCredentials(Set<String> creds) {
+        List<RequiredCredentialData> credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE);
+        updateRequiredCredentials(creds, credsData);
+    }
+
+    protected void updateRequiredCredentials(Set<String> creds, List<RequiredCredentialData> credsData) {
+        Set<String> already = new HashSet<String>();
+        for (RequiredCredentialData data : credsData) {
+            if (!creds.contains(data.getType())) {
+                noSQL.removeObject(data);
+            } else {
+                already.add(data.getType());
+            }
+        }
+        for (String cred : creds) {
+            // TODO
+            System.out.println("updating cred: " + cred);
+            // logger.info("updating cred: " + cred);
+            if (!already.contains(cred)) {
+                addRequiredCredential(cred);
+            }
+        }
+    }
+
+    @Override
+    public List<RequiredCredentialModel> getRequiredCredentials() {
+        return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_USER);
+    }
+
+    @Override
+    public List<RequiredCredentialModel> getRequiredApplicationCredentials() {
+        return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_RESOURCE);
+    }
+
+    @Override
+    public List<RequiredCredentialModel> getRequiredOAuthClientCredentials() {
+        return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE);
+    }
+
+    protected List<RequiredCredentialModel> getRequiredCredentials(int credentialType) {
+        List<RequiredCredentialData> credsData = getRequiredCredentialsData(credentialType);
+
+        List<RequiredCredentialModel> result = new ArrayList<RequiredCredentialModel>();
+        for (RequiredCredentialData data : credsData) {
+            RequiredCredentialModel model = new RequiredCredentialModel();
+            model.setFormLabel(data.getFormLabel());
+            model.setInput(data.isInput());
+            model.setSecret(data.isSecret());
+            model.setType(data.getType());
+
+            result.add(model);
+        }
+        return result;
+    }
+
+    protected List<RequiredCredentialData> getRequiredCredentialsData(int credentialType) {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("realmId", getOid())
+                .andCondition("clientType", credentialType)
+                .build();
+        return noSQL.loadObjects(RequiredCredentialData.class, query);
+    }
+
+    @Override
+    public boolean validatePassword(UserModel user, String password) {
+        Credentials.Status status = passwordCredentialHandler.validate(noSQL, ((UserAdapter)user).getUser(), password);
+        return status == Credentials.Status.VALID;
+    }
+
+    @Override
+    public boolean validateTOTP(UserModel user, String password, String token) {
+        Credentials.Status status = totpCredentialHandler.validate(noSQL, ((UserAdapter)user).getUser(), password, token, null);
+        return status == Credentials.Status.VALID;
+    }
+
+    @Override
+    public void updateCredential(UserModel user, UserCredentialModel cred) {
+        if (cred.getType().equals(CredentialRepresentation.PASSWORD)) {
+            passwordCredentialHandler.update(noSQL, ((UserAdapter)user).getUser(), cred.getValue(), null, null);
+        } else if (cred.getType().equals(CredentialRepresentation.TOTP)) {
+            totpCredentialHandler.update(noSQL, ((UserAdapter)user).getUser(), cred.getValue(), cred.getDevice(), null, null);
+        } else if (cred.getType().equals(CredentialRepresentation.CLIENT_CERT)) {
+            // TODO
+//            X509Certificate cert = null;
+//            try {
+//                cert = org.keycloak.PemUtils.decodeCertificate(cred.getValue());
+//            } catch (Exception e) {
+//                throw new RuntimeException(e);
+//            }
+//            X509CertificateCredentials creds = new X509CertificateCredentials(cert);
+//            idm.updateCredential(((UserAdapter)user).getUser(), creds);
+        }
+    }
+
+    @Override
+    public UserModel getUserBySocialLink(SocialLinkModel socialLink) {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("socialProvider", socialLink.getSocialProvider())
+                .andCondition("socialUsername", socialLink.getSocialUsername())
+                .andCondition("realmId", getOid())
+                .build();
+        SocialLinkData socialLinkData = noSQL.loadSingleObject(SocialLinkData.class, query);
+
+        if (socialLinkData == null) {
+            return null;
+        } else {
+            UserData userData = noSQL.loadObject(UserData.class, socialLinkData.getUserId());
+            // TODO: Add some checking if userData exists and programmatically remove binding if it doesn't? (There are more similar places where this should be handled)
+            return new UserAdapter(userData, noSQL);
+        }
+    }
+
+    @Override
+    public Set<SocialLinkModel> getSocialLinks(UserModel user) {
+        UserData userData = ((UserAdapter)user).getUser();
+        String userId = userData.getId();
+
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("userId", userId)
+                .build();
+        List<SocialLinkData> dbSocialLinks = noSQL.loadObjects(SocialLinkData.class, query);
+
+        Set<SocialLinkModel> result = new HashSet<SocialLinkModel>();
+        for (SocialLinkData socialLinkData : dbSocialLinks) {
+            SocialLinkModel model = new SocialLinkModel(socialLinkData.getSocialProvider(), socialLinkData.getSocialUsername());
+            result.add(model);
+        }
+        return result;
+    }
+
+    @Override
+    public void addSocialLink(UserModel user, SocialLinkModel socialLink) {
+        UserData userData = ((UserAdapter)user).getUser();
+        SocialLinkData socialLinkData = new SocialLinkData();
+        socialLinkData.setSocialProvider(socialLink.getSocialProvider());
+        socialLinkData.setSocialUsername(socialLink.getSocialUsername());
+        socialLinkData.setUserId(userData.getId());
+        socialLinkData.setRealmId(getOid());
+
+        noSQL.saveObject(socialLinkData);
+    }
+
+    @Override
+    public void removeSocialLink(UserModel user, SocialLinkModel socialLink) {
+        UserData userData = ((UserAdapter)user).getUser();
+        String userId = userData.getId();
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("socialProvider", socialLink.getSocialProvider())
+                .andCondition("socialUsername", socialLink.getSocialUsername())
+                .andCondition("userId", userId)
+                .build();
+        noSQL.removeObjects(SocialLinkData.class, query);
+    }
+
+    protected void updateRealm() {
+        noSQL.saveObject(realm);
+    }
+
+    protected RequiredCredentialModel initRequiredCredentialModel(String type) {
+        RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
+        if (model == null) {
+            throw new RuntimeException("Unknown credential type " + type);
+        }
+        return model;
+    }
+
+    @Override
+    public List<UserModel> searchForUserByAttributes(Map<String, String> attributes) {
+        NoSQLQueryBuilder queryBuilder = noSQL.createQueryBuilder();
+        for (Map.Entry<String, String> entry : attributes.entrySet()) {
+            if (entry.getKey().equals(UserModel.LOGIN_NAME)) {
+                queryBuilder.andCondition("loginName", entry.getValue());
+            } else if (entry.getKey().equalsIgnoreCase(UserModel.FIRST_NAME)) {
+                queryBuilder.andCondition(UserModel.FIRST_NAME, entry.getValue());
+
+            } else if (entry.getKey().equalsIgnoreCase(UserModel.LAST_NAME)) {
+                queryBuilder.andCondition(UserModel.LAST_NAME, entry.getValue());
+
+            } else if (entry.getKey().equalsIgnoreCase(UserModel.EMAIL)) {
+                queryBuilder.andCondition(UserModel.EMAIL, entry.getValue());
+            }
+        }
+        List<UserData> users = noSQL.loadObjects(UserData.class, queryBuilder.build());
+        List<UserModel> userModels = new ArrayList<UserModel>();
+        for (UserData user : users) {
+            userModels.add(new UserAdapter(user, noSQL));
+        }
+        return userModels;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java
new file mode 100644
index 0000000..7b2692f
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java
@@ -0,0 +1,52 @@
+package org.keycloak.models.mongo.keycloak.adapters;
+
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.keycloak.data.RoleData;
+
+/**
+ * Wrapper around RoleData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RoleAdapter implements RoleModel {
+
+    private final RoleData role;
+    private final NoSQL noSQL;
+
+    public RoleAdapter(RoleData roleData, NoSQL noSQL) {
+        this.role = roleData;
+        this.noSQL = noSQL;
+    }
+
+    @Override
+    public String getName() {
+        return role.getName();
+    }
+
+    @Override
+    public String getDescription() {
+        return role.getDescription();
+    }
+
+    @Override
+    public void setDescription(String description) {
+        role.setDescription(description);
+        noSQL.saveObject(role);
+    }
+
+    @Override
+    public String getId() {
+        return role.getId();
+    }
+
+    @Override
+    public void setName(String name) {
+        role.setName(name);
+        noSQL.saveObject(role);
+    }
+
+    public RoleData getRole() {
+        return role;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
new file mode 100644
index 0000000..c047361
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -0,0 +1,152 @@
+package org.keycloak.models.mongo.keycloak.adapters;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.keycloak.models.UserModel;
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.keycloak.data.UserData;
+
+/**
+ * Wrapper around UserData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserAdapter implements UserModel {
+
+    private final UserData user;
+    private final NoSQL noSQL;
+
+    public UserAdapter(UserData userData, NoSQL noSQL) {
+        this.user = userData;
+        this.noSQL = noSQL;
+    }
+
+    @Override
+    public String getLoginName() {
+        return user.getLoginName();
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return user.isEnabled();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        user.setEnabled(enabled);
+        noSQL.saveObject(user);
+    }
+
+    @Override
+    public String getFirstName() {
+        return user.getFirstName();
+    }
+
+    @Override
+    public void setFirstName(String firstName) {
+        user.setFirstName(firstName);
+        noSQL.saveObject(user);
+    }
+
+    @Override
+    public String getLastName() {
+        return user.getLastName();
+    }
+
+    @Override
+    public void setLastName(String lastName) {
+        user.setLastName(lastName);
+        noSQL.saveObject(user);
+    }
+
+    @Override
+    public String getEmail() {
+        return user.getEmail();
+    }
+
+    @Override
+    public void setEmail(String email) {
+        user.setEmail(email);
+        noSQL.saveObject(user);
+    }
+
+    @Override
+    public boolean isEmailVerified() {
+        return user.isEmailVerified();
+    }
+
+    @Override
+    public void setEmailVerified(boolean verified) {
+        user.setEmailVerified(verified);
+        noSQL.saveObject(user);
+    }
+
+    @Override
+    public void setAttribute(String name, String value) {
+        user.setAttribute(name, value);
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        user.removeAttribute(name);
+        noSQL.saveObject(user);
+    }
+
+    @Override
+    public String getAttribute(String name) {
+        return user.getAttribute(name);
+    }
+
+    @Override
+    public Map<String, String> getAttributes() {
+        return user.getAttributes();
+    }
+
+    public UserData getUser() {
+        return user;
+    }
+
+    @Override
+    public Set<RequiredAction> getRequiredActions() {
+        List<RequiredAction> actions = user.getRequiredActions();
+
+        // Compatibility with picketlink impl
+        if (actions == null) {
+            return Collections.emptySet();
+        } else {
+            Set<RequiredAction> s = new HashSet<RequiredAction>();
+            for (RequiredAction a : actions) {
+                s.add(a);
+            }
+            return Collections.unmodifiableSet(s);
+        }
+    }
+
+    @Override
+    public void addRequiredAction(RequiredAction action) {
+        // Push action only if it's not already here
+        if (user.getRequiredActions() == null || !user.getRequiredActions().contains(action)) {
+            noSQL.pushItemToList(user, "requiredActions", action);
+        }
+    }
+
+    @Override
+    public void removeRequiredAction(RequiredAction action) {
+        noSQL.pullItemFromList(user, "requiredActions", action);
+    }
+
+    @Override
+    public boolean isTotp() {
+        return user.isTotp();
+    }
+
+    @Override
+    public void setTotp(boolean totp) {
+        user.setTotp(totp);
+        noSQL.saveObject(user);
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/PasswordCredentialHandler.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/PasswordCredentialHandler.java
new file mode 100644
index 0000000..719760a
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/PasswordCredentialHandler.java
@@ -0,0 +1,154 @@
+package org.keycloak.models.mongo.keycloak.credentials;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.UUID;
+
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+import org.keycloak.models.mongo.keycloak.data.UserData;
+import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData;
+import org.picketlink.idm.credential.Credentials;
+import org.picketlink.idm.credential.encoder.PasswordEncoder;
+import org.picketlink.idm.credential.encoder.SHAPasswordEncoder;
+
+/**
+ * Defacto forked from {@link org.picketlink.idm.credential.handler.PasswordCredentialHandler}
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PasswordCredentialHandler {
+
+    private static final String DEFAULT_SALT_ALGORITHM = "SHA1PRNG";
+
+    /**
+     * <p>
+     * Stores a <b>stateless</b> instance of {@link org.picketlink.idm.credential.encoder.PasswordEncoder} that should be used to encode passwords.
+     * </p>
+     */
+    public static final String PASSWORD_ENCODER = "PASSWORD_ENCODER";
+
+    private PasswordEncoder passwordEncoder = new SHAPasswordEncoder(512);;
+
+    public PasswordCredentialHandler(Map<String, Object> options) {
+        setup(options);
+    }
+
+    private void setup(Map<String, Object> options) {
+        if (options != null) {
+            Object providedEncoder = options.get(PASSWORD_ENCODER);
+
+            if (providedEncoder != null) {
+                if (PasswordEncoder.class.isInstance(providedEncoder)) {
+                    this.passwordEncoder = (PasswordEncoder) providedEncoder;
+                } else {
+                    throw new IllegalArgumentException("The password encoder [" + providedEncoder
+                            + "] must be an instance of " + PasswordEncoder.class.getName());
+                }
+            }
+        }
+    }
+
+    public Credentials.Status validate(NoSQL noSQL, UserData user, String passwordToValidate) {
+        Credentials.Status status = Credentials.Status.INVALID;
+
+        user = noSQL.loadObject(UserData.class, user.getId());
+
+        // If the user for the provided username cannot be found we fail validation
+        if (user != null) {
+            if (user.isEnabled()) {
+                NoSQLQuery query = noSQL.createQueryBuilder()
+                        .andCondition("userId", user.getId())
+                        .build();
+                PasswordData passwordData = noSQL.loadSingleObject(PasswordData.class, query);
+
+                // If the stored hash is null we automatically fail validation
+                if (passwordData != null) {
+                    // TODO: Status.INVALID should have bigger priority than Status.EXPIRED?
+                    if (!isCredentialExpired(passwordData.getExpiryDate())) {
+
+                        boolean matches = this.passwordEncoder.verify(saltPassword(passwordToValidate, passwordData.getSalt()), passwordData.getEncodedHash());
+
+                        if (matches) {
+                            status = Credentials.Status.VALID;
+                        }
+                    } else {
+                        status = Credentials.Status.EXPIRED;
+                    }
+                }
+            } else {
+                status = Credentials.Status.ACCOUNT_DISABLED;
+            }
+        }
+
+        return status;
+    }
+
+    public void update(NoSQL noSQL, UserData user, String password,
+                       Date effectiveDate, Date expiryDate) {
+
+        // Delete existing password of user
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("userId", user.getId())
+                .build();
+        noSQL.removeObjects(PasswordData.class, query);
+
+        PasswordData passwordData = new PasswordData();
+
+        String passwordSalt = generateSalt();
+
+        passwordData.setSalt(passwordSalt);
+        passwordData.setEncodedHash(this.passwordEncoder.encode(saltPassword(password, passwordSalt)));
+
+        if (effectiveDate != null) {
+            passwordData.setEffectiveDate(effectiveDate);
+        }
+
+        passwordData.setExpiryDate(expiryDate);
+
+        passwordData.setUserId(user.getId());
+
+        noSQL.saveObject(passwordData);
+    }
+
+    /**
+     * <p>
+     * Salt the give <code>rawPassword</code> with the specified <code>salt</code> value.
+     * </p>
+     *
+     * @param rawPassword
+     * @param salt
+     * @return
+     */
+    private String saltPassword(String rawPassword, String salt) {
+        return salt + rawPassword;
+    }
+
+    /**
+     * <p>
+     * Generates a random string to be used as a salt for passwords.
+     * </p>
+     *
+     * @return
+     */
+    private String generateSalt() {
+        // TODO: always returns same salt (See https://issues.jboss.org/browse/PLINK-258)
+        /*SecureRandom pseudoRandom = null;
+
+        try {
+            pseudoRandom = SecureRandom.getInstance(DEFAULT_SALT_ALGORITHM);
+            pseudoRandom.setSeed(1024);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Error getting SecureRandom instance: " + DEFAULT_SALT_ALGORITHM, e);
+        }
+
+        return String.valueOf(pseudoRandom.nextLong());*/
+        return UUID.randomUUID().toString();
+    }
+
+    public static boolean isCredentialExpired(Date expiryDate) {
+        return expiryDate != null && new Date().compareTo(expiryDate) > 0;
+    }
+
+
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/TOTPCredentialHandler.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/TOTPCredentialHandler.java
new file mode 100644
index 0000000..b8f02e7
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/TOTPCredentialHandler.java
@@ -0,0 +1,138 @@
+package org.keycloak.models.mongo.keycloak.credentials;
+
+import java.util.Date;
+import java.util.Map;
+
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+import org.keycloak.models.mongo.keycloak.data.UserData;
+import org.keycloak.models.mongo.keycloak.data.credentials.OTPData;
+import org.picketlink.idm.credential.Credentials;
+import org.picketlink.idm.credential.util.TimeBasedOTP;
+
+import static org.picketlink.common.util.StringUtil.isNullOrEmpty;
+import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_ALGORITHM;
+import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_DELAY_WINDOW;
+import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_INTERVAL_SECONDS;
+import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_NUMBER_DIGITS;
+
+/**
+ * Defacto forked from {@link org.picketlink.idm.credential.handler.TOTPCredentialHandler}
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TOTPCredentialHandler extends PasswordCredentialHandler {
+
+    public static final String ALGORITHM = "ALGORITHM";
+    public static final String INTERVAL_SECONDS = "INTERVAL_SECONDS";
+    public static final String NUMBER_DIGITS = "NUMBER_DIGITS";
+    public static final String DELAY_WINDOW = "DELAY_WINDOW";
+    public static final String DEFAULT_DEVICE = "DEFAULT_DEVICE";
+
+    private TimeBasedOTP totp;
+
+    public TOTPCredentialHandler(Map<String, Object> options) {
+        super(options);
+        setup(options);
+    }
+
+    private void setup(Map<String, Object> options) {
+        String algorithm = getConfigurationProperty(options, ALGORITHM, DEFAULT_ALGORITHM);
+        String intervalSeconds = getConfigurationProperty(options, INTERVAL_SECONDS, "" + DEFAULT_INTERVAL_SECONDS);
+        String numberDigits = getConfigurationProperty(options, NUMBER_DIGITS, "" + DEFAULT_NUMBER_DIGITS);
+        String delayWindow = getConfigurationProperty(options, DELAY_WINDOW, "" + DEFAULT_DELAY_WINDOW);
+
+        this.totp = new TimeBasedOTP(algorithm, Integer.parseInt(numberDigits), Integer.valueOf(intervalSeconds), Integer.valueOf(delayWindow));
+    }
+
+    public Credentials.Status validate(NoSQL noSQL, UserData user, String passwordToValidate, String token, String device) {
+        Credentials.Status status = super.validate(noSQL, user, passwordToValidate);
+
+        if (Credentials.Status.VALID != status) {
+            return status;
+        }
+
+        device = getDevice(device);
+
+        user = noSQL.loadObject(UserData.class, user.getId());
+
+        // If the user for the provided username cannot be found we fail validation
+        if (user != null) {
+            if (user.isEnabled()) {
+
+                // Try to find OTP based on userId and device (For now assume that this is unique combination)
+                NoSQLQuery query = noSQL.createQueryBuilder()
+                        .andCondition("userId", user.getId())
+                        .andCondition("device", device)
+                        .build();
+                OTPData otpData = noSQL.loadSingleObject(OTPData.class, query);
+
+                // If the stored OTP is null we automatically fail validation
+                if (otpData != null) {
+                    // TODO: Status.INVALID should have bigger priority than Status.EXPIRED?
+                    if (!PasswordCredentialHandler.isCredentialExpired(otpData.getExpiryDate())) {
+                        boolean isValid = this.totp.validate(token, otpData.getSecretKey().getBytes());
+                        if (!isValid) {
+                            status = Credentials.Status.INVALID;
+                        }
+                    }  else {
+                        status = Credentials.Status.EXPIRED;
+                    }
+                } else {
+                    status = Credentials.Status.UNVALIDATED;
+                }
+            } else {
+                status = Credentials.Status.ACCOUNT_DISABLED;
+            }
+        } else {
+            status = Credentials.Status.INVALID;
+        }
+
+        return status;
+    }
+
+    public void update(NoSQL noSQL, UserData user, String secret, String device, Date effectiveDate, Date expiryDate) {
+        device = getDevice(device);
+
+        // Try to look if user already has otp (Right now, supports just one OTP per user)
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("userId", user.getId())
+                .andCondition("device", device)
+                .build();
+
+        OTPData otpData = noSQL.loadSingleObject(OTPData.class, query);
+        if (otpData == null) {
+            otpData = new OTPData();
+        }
+
+        otpData.setSecretKey(secret);
+        otpData.setDevice(device);
+
+        if (effectiveDate != null) {
+            otpData.setEffectiveDate(effectiveDate);
+        }
+
+        otpData.setExpiryDate(expiryDate);
+        otpData.setUserId(user.getId());
+
+        noSQL.saveObject(otpData);
+    }
+
+    private String getDevice(String device) {
+        if (isNullOrEmpty(device)) {
+            device = DEFAULT_DEVICE;
+        }
+
+        return device;
+    }
+
+    private String getConfigurationProperty(Map<String, Object> options, String key, String defaultValue) {
+        Object value = options.get(key);
+
+        if (value != null) {
+            return String.valueOf(value);
+        }
+
+        return defaultValue;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/ApplicationData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/ApplicationData.java
new file mode 100644
index 0000000..5ceb788
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/ApplicationData.java
@@ -0,0 +1,109 @@
+package org.keycloak.models.mongo.keycloak.data;
+
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.NoSQLCollection;
+import org.keycloak.models.mongo.api.NoSQLField;
+import org.keycloak.models.mongo.api.NoSQLId;
+import org.keycloak.models.mongo.api.NoSQLObject;
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "applications")
+public class ApplicationData implements NoSQLObject {
+
+    private String id;
+    private String name;
+    private boolean enabled;
+    private boolean surrogateAuthRequired;
+    private String managementUrl;
+    private String baseUrl;
+
+    private String resourceUserId;
+    private String realmId;
+
+    @NoSQLId
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @NoSQLField
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @NoSQLField
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    @NoSQLField
+    public boolean isSurrogateAuthRequired() {
+        return surrogateAuthRequired;
+    }
+
+    public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
+        this.surrogateAuthRequired = surrogateAuthRequired;
+    }
+
+    @NoSQLField
+    public String getManagementUrl() {
+        return managementUrl;
+    }
+
+    public void setManagementUrl(String managementUrl) {
+        this.managementUrl = managementUrl;
+    }
+
+    @NoSQLField
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public void setBaseUrl(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
+    @NoSQLField
+    public String getResourceUserId() {
+        return resourceUserId;
+    }
+
+    public void setResourceUserId(String resourceUserId) {
+        this.resourceUserId = resourceUserId;
+    }
+
+    @NoSQLField
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    @Override
+    public void afterRemove(NoSQL noSQL) {
+        // Remove resourceUser of this application
+        noSQL.removeObject(UserData.class, resourceUserId);
+
+        // Remove all roles, which belongs to this application
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("applicationId", id)
+                .build();
+        noSQL.removeObjects(RoleData.class, query);
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/OTPData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/OTPData.java
new file mode 100644
index 0000000..8ab31a6
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/OTPData.java
@@ -0,0 +1,66 @@
+package org.keycloak.models.mongo.keycloak.data.credentials;
+
+import java.util.Date;
+
+import org.keycloak.models.mongo.api.AbstractNoSQLObject;
+import org.keycloak.models.mongo.api.NoSQLCollection;
+import org.keycloak.models.mongo.api.NoSQLField;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "otpCredentials")
+public class OTPData extends AbstractNoSQLObject {
+
+    private Date effectiveDate = new Date();
+    private Date expiryDate;
+    private String secretKey;
+    private String device;
+
+    private String userId;
+
+    @NoSQLField
+    public Date getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    public void setEffectiveDate(Date effectiveDate) {
+        this.effectiveDate = effectiveDate;
+    }
+
+    @NoSQLField
+    public Date getExpiryDate() {
+        return expiryDate;
+    }
+
+    public void setExpiryDate(Date expiryDate) {
+        this.expiryDate = expiryDate;
+    }
+
+    @NoSQLField
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public void setSecretKey(String secretKey) {
+        this.secretKey = secretKey;
+    }
+
+    @NoSQLField
+    public String getDevice() {
+        return device;
+    }
+
+    public void setDevice(String device) {
+        this.device = device;
+    }
+
+    @NoSQLField
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/PasswordData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/PasswordData.java
new file mode 100644
index 0000000..7480e1f
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/PasswordData.java
@@ -0,0 +1,66 @@
+package org.keycloak.models.mongo.keycloak.data.credentials;
+
+import java.util.Date;
+
+import org.keycloak.models.mongo.api.AbstractNoSQLObject;
+import org.keycloak.models.mongo.api.NoSQLCollection;
+import org.keycloak.models.mongo.api.NoSQLField;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "passwordCredentials")
+public class PasswordData extends AbstractNoSQLObject {
+
+    private Date effectiveDate = new Date();
+    private Date expiryDate;
+    private String encodedHash;
+    private String salt;
+
+    private String userId;
+
+    @NoSQLField
+    public Date getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    public void setEffectiveDate(Date effectiveDate) {
+        this.effectiveDate = effectiveDate;
+    }
+
+    @NoSQLField
+    public Date getExpiryDate() {
+        return expiryDate;
+    }
+
+    public void setExpiryDate(Date expiryDate) {
+        this.expiryDate = expiryDate;
+    }
+
+    @NoSQLField
+    public String getEncodedHash() {
+        return encodedHash;
+    }
+
+    public void setEncodedHash(String encodedHash) {
+        this.encodedHash = encodedHash;
+    }
+
+    @NoSQLField
+    public String getSalt() {
+        return salt;
+    }
+
+    public void setSalt(String salt) {
+        this.salt = salt;
+    }
+
+    @NoSQLField
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/OAuthClientData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/OAuthClientData.java
new file mode 100644
index 0000000..67f74ee
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/OAuthClientData.java
@@ -0,0 +1,62 @@
+package org.keycloak.models.mongo.keycloak.data;
+
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.NoSQLCollection;
+import org.keycloak.models.mongo.api.NoSQLField;
+import org.keycloak.models.mongo.api.NoSQLId;
+import org.keycloak.models.mongo.api.NoSQLObject;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "oauthClients")
+public class OAuthClientData implements NoSQLObject {
+
+    private String id;
+    private String baseUrl;
+
+    private String oauthAgentId;
+    private String realmId;
+
+    @NoSQLId
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @NoSQLField
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public void setBaseUrl(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
+    @NoSQLField
+    public String getOauthAgentId() {
+        return oauthAgentId;
+    }
+
+    public void setOauthAgentId(String oauthUserId) {
+        this.oauthAgentId = oauthUserId;
+    }
+
+    @NoSQLField
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    @Override
+    public void afterRemove(NoSQL noSQL) {
+        // Remove user of this oauthClient
+        noSQL.removeObject(UserData.class, oauthAgentId);
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RealmData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RealmData.java
new file mode 100644
index 0000000..5247d60
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RealmData.java
@@ -0,0 +1,219 @@
+package org.keycloak.models.mongo.keycloak.data;
+
+import java.util.List;
+
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.NoSQLCollection;
+import org.keycloak.models.mongo.api.NoSQLField;
+import org.keycloak.models.mongo.api.NoSQLId;
+import org.keycloak.models.mongo.api.NoSQLObject;
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "realms")
+public class RealmData implements NoSQLObject {
+
+    private String oid;
+
+    private String id;
+    private String name;
+    private boolean enabled;
+    private boolean sslNotRequired;
+    private boolean cookieLoginAllowed;
+    private boolean registrationAllowed;
+    private boolean verifyEmail;
+    private boolean resetPasswordAllowed;
+    private boolean social;
+    private boolean automaticRegistrationAfterSocialLogin;
+    private int tokenLifespan;
+    private int accessCodeLifespan;
+    private int accessCodeLifespanUserAction;
+    private String publicKeyPem;
+    private String privateKeyPem;
+
+    private List<String> defaultRoles;
+    private List<String> realmAdmins;
+
+    @NoSQLId
+    public String getOid() {
+        return oid;
+    }
+
+    public void setOid(String oid) {
+        this.oid = oid;
+    }
+
+    @NoSQLField
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @NoSQLField
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String realmName) {
+        this.name = realmName;
+    }
+
+    @NoSQLField
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    @NoSQLField
+    public boolean isSslNotRequired() {
+        return sslNotRequired;
+    }
+
+    public void setSslNotRequired(boolean sslNotRequired) {
+        this.sslNotRequired = sslNotRequired;
+    }
+
+    @NoSQLField
+    public boolean isCookieLoginAllowed() {
+        return cookieLoginAllowed;
+    }
+
+    public void setCookieLoginAllowed(boolean cookieLoginAllowed) {
+        this.cookieLoginAllowed = cookieLoginAllowed;
+    }
+
+    @NoSQLField
+    public boolean isRegistrationAllowed() {
+        return registrationAllowed;
+    }
+
+    public void setRegistrationAllowed(boolean registrationAllowed) {
+        this.registrationAllowed = registrationAllowed;
+    }
+
+    @NoSQLField
+    public boolean isVerifyEmail() {
+        return verifyEmail;
+    }
+
+    public void setVerifyEmail(boolean verifyEmail) {
+        this.verifyEmail = verifyEmail;
+    }
+
+    @NoSQLField
+    public boolean isResetPasswordAllowed() {
+        return resetPasswordAllowed;
+    }
+
+    public void setResetPasswordAllowed(boolean resetPasswordAllowed) {
+        this.resetPasswordAllowed = resetPasswordAllowed;
+    }
+
+    @NoSQLField
+    public boolean isSocial() {
+        return social;
+    }
+
+    public void setSocial(boolean social) {
+        this.social = social;
+    }
+
+    @NoSQLField
+    public boolean isAutomaticRegistrationAfterSocialLogin() {
+        return automaticRegistrationAfterSocialLogin;
+    }
+
+    public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
+        this.automaticRegistrationAfterSocialLogin = automaticRegistrationAfterSocialLogin;
+    }
+
+    @NoSQLField
+    public int getTokenLifespan() {
+        return tokenLifespan;
+    }
+
+    public void setTokenLifespan(int tokenLifespan) {
+        this.tokenLifespan = tokenLifespan;
+    }
+
+    @NoSQLField
+    public int getAccessCodeLifespan() {
+        return accessCodeLifespan;
+    }
+
+    public void setAccessCodeLifespan(int accessCodeLifespan) {
+        this.accessCodeLifespan = accessCodeLifespan;
+    }
+
+    @NoSQLField
+    public int getAccessCodeLifespanUserAction() {
+        return accessCodeLifespanUserAction;
+    }
+
+    public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
+        this.accessCodeLifespanUserAction = accessCodeLifespanUserAction;
+    }
+
+    @NoSQLField
+    public String getPublicKeyPem() {
+        return publicKeyPem;
+    }
+
+    public void setPublicKeyPem(String publicKeyPem) {
+        this.publicKeyPem = publicKeyPem;
+    }
+
+    @NoSQLField
+    public String getPrivateKeyPem() {
+        return privateKeyPem;
+    }
+
+    public void setPrivateKeyPem(String privateKeyPem) {
+        this.privateKeyPem = privateKeyPem;
+    }
+
+    @NoSQLField
+    public List<String> getDefaultRoles() {
+        return defaultRoles;
+    }
+
+    public void setDefaultRoles(List<String> defaultRoles) {
+        this.defaultRoles = defaultRoles;
+    }
+
+    @NoSQLField
+    public List<String> getRealmAdmins() {
+        return realmAdmins;
+    }
+
+    public void setRealmAdmins(List<String> realmAdmins) {
+        this.realmAdmins = realmAdmins;
+    }
+
+    @Override
+    public void afterRemove(NoSQL noSQL) {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("realmId", oid)
+                .build();
+
+        // Remove all users of this realm
+        noSQL.removeObjects(UserData.class, query);
+
+        // Remove all requiredCredentials of this realm
+        noSQL.removeObjects(RequiredCredentialData.class, query);
+
+        // Remove all roles of this realm
+        noSQL.removeObjects(RoleData.class, query);
+
+        // Remove all applications of this realm
+        noSQL.removeObjects(ApplicationData.class, query);
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RequiredCredentialData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RequiredCredentialData.java
new file mode 100644
index 0000000..e46ee9f
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RequiredCredentialData.java
@@ -0,0 +1,90 @@
+package org.keycloak.models.mongo.keycloak.data;
+
+import org.keycloak.models.mongo.api.AbstractNoSQLObject;
+import org.keycloak.models.mongo.api.NoSQLCollection;
+import org.keycloak.models.mongo.api.NoSQLField;
+import org.keycloak.models.mongo.api.NoSQLId;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "requiredCredentials")
+public class RequiredCredentialData extends AbstractNoSQLObject {
+
+    public static final int CLIENT_TYPE_USER = 1;
+    public static final int CLIENT_TYPE_RESOURCE = 2;
+    public static final int CLIENT_TYPE_OAUTH_RESOURCE = 3;
+
+    private String id;
+
+    private String type;
+    private boolean input;
+    private boolean secret;
+    private String formLabel;
+
+    private String realmId;
+    private int clientType;
+
+    @NoSQLId
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @NoSQLField
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    @NoSQLField
+    public boolean isInput() {
+        return input;
+    }
+
+    public void setInput(boolean input) {
+        this.input = input;
+    }
+
+    @NoSQLField
+    public boolean isSecret() {
+        return secret;
+    }
+
+    public void setSecret(boolean secret) {
+        this.secret = secret;
+    }
+
+    @NoSQLField
+    public String getFormLabel() {
+        return formLabel;
+    }
+
+    public void setFormLabel(String formLabel) {
+        this.formLabel = formLabel;
+    }
+
+    @NoSQLField
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    @NoSQLField
+    public int getClientType() {
+        return clientType;
+    }
+
+    public void setClientType(int clientType) {
+        this.clientType = clientType;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RoleData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RoleData.java
new file mode 100644
index 0000000..29bc1f8
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RoleData.java
@@ -0,0 +1,98 @@
+package org.keycloak.models.mongo.keycloak.data;
+
+import java.util.List;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.NoSQLCollection;
+import org.keycloak.models.mongo.api.NoSQLField;
+import org.keycloak.models.mongo.api.NoSQLId;
+import org.keycloak.models.mongo.api.NoSQLObject;
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "roles")
+public class RoleData implements NoSQLObject {
+
+    private static final Logger logger = Logger.getLogger(RoleData.class);
+
+    private String id;
+    private String name;
+    private String description;
+
+    private String realmId;
+    private String applicationId;
+
+    @NoSQLId
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @NoSQLField
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @NoSQLField
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @NoSQLField
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    @NoSQLField
+    public String getApplicationId() {
+        return applicationId;
+    }
+
+    public void setApplicationId(String applicationId) {
+        this.applicationId = applicationId;
+    }
+
+    @Override
+    public void afterRemove(NoSQL noSQL) {
+        // Remove this role from all users, which has it
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("roleIds", id)
+                .build();
+
+        List<UserData> users = noSQL.loadObjects(UserData.class, query);
+        for (UserData user : users) {
+            logger.info("Removing role " + getName() + " from user " + user.getLoginName());
+            noSQL.pullItemFromList(user, "roleIds", getId());
+        }
+
+        // Remove this scope from all users, which has it
+        query = noSQL.createQueryBuilder()
+                .andCondition("scopeIds", id)
+                .build();
+
+        users = noSQL.loadObjects(UserData.class, query);
+        for (UserData user : users) {
+            logger.info("Removing scope " + getName() + " from user " + user.getLoginName());
+            noSQL.pullItemFromList(user, "scopeIds", getId());
+        }
+
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/SocialLinkData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/SocialLinkData.java
new file mode 100644
index 0000000..37ea43d
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/SocialLinkData.java
@@ -0,0 +1,55 @@
+package org.keycloak.models.mongo.keycloak.data;
+
+import org.keycloak.models.mongo.api.AbstractNoSQLObject;
+import org.keycloak.models.mongo.api.NoSQLCollection;
+import org.keycloak.models.mongo.api.NoSQLField;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "socialLinks")
+public class SocialLinkData extends AbstractNoSQLObject {
+
+    private String socialUsername;
+    private String socialProvider;
+    private String userId;
+    // realmId is needed to allow searching as combination socialUsername+socialProvider may not be unique
+    // (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2")
+    private String realmId;
+
+    @NoSQLField
+    public String getSocialUsername() {
+        return socialUsername;
+    }
+
+    public void setSocialUsername(String socialUsername) {
+        this.socialUsername = socialUsername;
+    }
+
+    @NoSQLField
+    public String getSocialProvider() {
+        return socialProvider;
+    }
+
+    public void setSocialProvider(String socialProvider) {
+        this.socialProvider = socialProvider;
+    }
+
+    @NoSQLField
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    @NoSQLField
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/UserData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/UserData.java
new file mode 100644
index 0000000..cfeb67d
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/UserData.java
@@ -0,0 +1,167 @@
+package org.keycloak.models.mongo.keycloak.data;
+
+import java.util.List;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.mongo.api.AbstractAttributedNoSQLObject;
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.NoSQLCollection;
+import org.keycloak.models.mongo.api.NoSQLField;
+import org.keycloak.models.mongo.api.NoSQLId;
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "users")
+public class UserData extends AbstractAttributedNoSQLObject {
+
+    private static final Logger logger = Logger.getLogger(UserData.class);
+
+    private String id;
+    private String loginName;
+    private String firstName;
+    private String lastName;
+    private String email;
+    private boolean emailVerified;
+    private boolean totp;
+    private boolean enabled;
+
+    private String realmId;
+
+    private List<String> roleIds;
+    private List<String> scopeIds;
+    private List<UserModel.RequiredAction> requiredActions;
+
+    @NoSQLId
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @NoSQLField
+    public String getLoginName() {
+        return loginName;
+    }
+
+    public void setLoginName(String loginName) {
+        this.loginName = loginName;
+    }
+
+    @NoSQLField
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+    }
+
+    @NoSQLField
+    public String getLastName() {
+        return lastName;
+    }
+
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+    }
+
+    @NoSQLField
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    @NoSQLField
+    public boolean isEmailVerified() {
+        return emailVerified;
+    }
+
+    public void setEmailVerified(boolean emailVerified) {
+        this.emailVerified = emailVerified;
+    }
+
+    @NoSQLField
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    @NoSQLField
+    public boolean isTotp() {
+        return totp;
+    }
+
+    public void setTotp(boolean totp) {
+        this.totp = totp;
+    }
+
+    @NoSQLField
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    @NoSQLField
+    public List<String> getRoleIds() {
+        return roleIds;
+    }
+
+    public void setRoleIds(List<String> roleIds) {
+        this.roleIds = roleIds;
+    }
+
+    @NoSQLField
+    public List<String> getScopeIds() {
+        return scopeIds;
+    }
+
+    public void setScopeIds(List<String> scopeIds) {
+        this.scopeIds = scopeIds;
+    }
+
+    @NoSQLField
+    public List<UserModel.RequiredAction> getRequiredActions() {
+        return requiredActions;
+    }
+
+    public void setRequiredActions(List<UserModel.RequiredAction> requiredActions) {
+        this.requiredActions = requiredActions;
+    }
+
+    @Override
+    public void afterRemove(NoSQL noSQL) {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("userId", id)
+                .build();
+
+        // Remove social links and passwords of this user
+        noSQL.removeObjects(SocialLinkData.class, query);
+        noSQL.removeObjects(PasswordData.class, query);
+
+        // Remove this user from all realms, which have him as an admin
+        NoSQLQuery realmQuery = noSQL.createQueryBuilder()
+                .andCondition("realmAdmins", id)
+                .build();
+
+        List<RealmData> realms = noSQL.loadObjects(RealmData.class, realmQuery);
+        for (RealmData realm : realms) {
+            logger.info("Removing admin user " + getLoginName() + " from realm " + realm.getId());
+            noSQL.pullItemFromList(realm, "realmAdmins", getId());
+        }
+    }
+}
diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java
new file mode 100644
index 0000000..8f6b6f8
--- /dev/null
+++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java
@@ -0,0 +1,43 @@
+package org.keycloak.models.mongo.test;
+
+import java.util.List;
+
+import org.keycloak.models.mongo.api.AbstractNoSQLObject;
+import org.keycloak.models.mongo.api.NoSQLField;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class Address extends AbstractNoSQLObject {
+
+    private String street;
+    private int number;
+    private List<String> flatNumbers;
+
+    @NoSQLField
+    public String getStreet() {
+        return street;
+    }
+
+    public void setStreet(String street) {
+        this.street = street;
+    }
+
+    @NoSQLField
+    public int getNumber() {
+        return number;
+    }
+
+    public void setNumber(int number) {
+        this.number = number;
+    }
+
+    @NoSQLField
+    public List<String> getFlatNumbers() {
+        return flatNumbers;
+    }
+
+    public void setFlatNumbers(List<String> flatNumbers) {
+        this.flatNumbers = flatNumbers;
+    }
+}
diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoDBModelTest.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoDBModelTest.java
new file mode 100644
index 0000000..262ade8
--- /dev/null
+++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoDBModelTest.java
@@ -0,0 +1,111 @@
+package org.keycloak.models.mongo.test;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.mongodb.DB;
+import com.mongodb.MongoClient;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.keycloak.models.mongo.api.NoSQL;
+import org.keycloak.models.mongo.api.NoSQLObject;
+import org.keycloak.models.mongo.api.query.NoSQLQuery;
+import org.keycloak.models.mongo.impl.MongoDBImpl;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MongoDBModelTest {
+
+    private static final Class<? extends NoSQLObject>[] MANAGED_DATA_TYPES = (Class<? extends NoSQLObject>[])new Class<?>[] {
+            Person.class,
+            Address.class,
+    };
+
+    private MongoClient mongoClient;
+    private NoSQL mongoDB;
+
+    @Before
+    public void before() throws Exception {
+        try {
+            // TODO: authentication support
+            mongoClient = new MongoClient("localhost", 27017);
+
+            DB db = mongoClient.getDB("keycloakTest");
+            mongoDB = new MongoDBImpl(db, true, MANAGED_DATA_TYPES);
+
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @After
+    public void after() throws Exception {
+        mongoClient.close();
+    }
+
+    // @Test
+    public void mongoModelTest() throws Exception {
+        // Add some user
+        Person john = new Person();
+        john.setFirstName("john");
+        john.setAge(25);
+        john.setGender(Person.Gender.MALE);
+
+        mongoDB.saveObject(john);
+
+        // Add another user
+        Person mary = new Person();
+        mary.setFirstName("mary");
+        mary.setKids(Arrays.asList(new String[] {"Peter", "Paul", "Wendy"}));
+
+        Address addr1 = new Address();
+        addr1.setStreet("Elm");
+        addr1.setNumber(5);
+        addr1.setFlatNumbers(Arrays.asList(new String[] {"flat1", "flat2"}));
+        Address addr2 = new Address();
+        List<Address> addresses = new ArrayList<Address>();
+        addresses.add(addr1);
+        addresses.add(addr2);
+
+        mary.setAddresses(addresses);
+        mary.setMainAddress(addr1);
+        mary.setGender(Person.Gender.FEMALE);
+        mary.setGenders(Arrays.asList(new Person.Gender[] {Person.Gender.FEMALE}));
+        mongoDB.saveObject(mary);
+
+        Assert.assertEquals(2, mongoDB.loadObjects(Person.class, mongoDB.createQueryBuilder().build()).size());
+
+        NoSQLQuery query = mongoDB.createQueryBuilder().andCondition("addresses.flatNumbers", "flat1").build();
+        List<Person> persons = mongoDB.loadObjects(Person.class, query);
+        Assert.assertEquals(1, persons.size());
+        mary = persons.get(0);
+        Assert.assertEquals(mary.getFirstName(), "mary");
+        Assert.assertTrue(mary.getKids().contains("Paul"));
+        Assert.assertEquals(2, mary.getAddresses().size());
+        Assert.assertEquals(Address.class, mary.getAddresses().get(0).getClass());
+
+        // Test push/pull
+        mongoDB.pushItemToList(mary, "kids", "Pauline");
+        mongoDB.pullItemFromList(mary, "kids", "Paul");
+
+        Address addr3 = new Address();
+        addr3.setNumber(6);
+        addr3.setStreet("Broadway");
+        mongoDB.pushItemToList(mary, "addresses", addr3);
+
+        mary = mongoDB.loadObject(Person.class, mary.getId());
+        Assert.assertEquals(3, mary.getKids().size());
+        Assert.assertTrue(mary.getKids().contains("Pauline"));
+        Assert.assertFalse(mary.getKids().contains("Paul"));
+        Assert.assertEquals(3, mary.getAddresses().size());
+        Address mainAddress = mary.getMainAddress();
+        Assert.assertEquals("Elm", mainAddress.getStreet());
+        Assert.assertEquals(5, mainAddress.getNumber());
+        Assert.assertEquals(Person.Gender.FEMALE, mary.getGender());
+        Assert.assertTrue(mary.getGenders().contains(Person.Gender.FEMALE));
+    }
+}
diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java
new file mode 100644
index 0000000..ab2ded3
--- /dev/null
+++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java
@@ -0,0 +1,101 @@
+package org.keycloak.models.mongo.test;
+
+import java.util.List;
+
+import org.keycloak.models.mongo.api.AbstractNoSQLObject;
+import org.keycloak.models.mongo.api.NoSQLCollection;
+import org.keycloak.models.mongo.api.NoSQLField;
+import org.keycloak.models.mongo.api.NoSQLId;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "persons")
+public class Person extends AbstractNoSQLObject {
+
+    private String id;
+    private String firstName;
+    private int age;
+    private List<String> kids;
+    private List<Address> addresses;
+    private Address mainAddress;
+    private Gender gender;
+    private List<Gender> genders;
+
+
+    @NoSQLId
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @NoSQLField
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+    }
+
+    @NoSQLField
+    public int getAge() {
+        return age;
+    }
+
+    public void setAge(int age) {
+        this.age = age;
+    }
+
+    @NoSQLField
+    public Gender getGender() {
+        return gender;
+    }
+
+    public void setGender(Gender gender) {
+        this.gender = gender;
+    }
+
+    @NoSQLField
+    public List<Gender> getGenders() {
+        return genders;
+    }
+
+    public void setGenders(List<Gender> genders) {
+        this.genders = genders;
+    }
+
+    @NoSQLField
+    public List<String> getKids() {
+        return kids;
+    }
+
+    public void setKids(List<String> kids) {
+        this.kids = kids;
+    }
+
+    @NoSQLField
+    public List<Address> getAddresses() {
+        return addresses;
+    }
+
+    public void setAddresses(List<Address> addresses) {
+        this.addresses = addresses;
+    }
+
+    @NoSQLField
+    public Address getMainAddress() {
+        return mainAddress;
+    }
+
+    public void setMainAddress(Address mainAddress) {
+        this.mainAddress = mainAddress;
+    }
+
+    public static enum Gender {
+        MALE, FEMALE
+    }
+}
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/PicketlinkKeycloakSession.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/PicketlinkKeycloakSession.java
index 4139e03..8e2a75d 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/PicketlinkKeycloakSession.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/PicketlinkKeycloakSession.java
@@ -6,6 +6,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.picketlink.mappings.RealmData;
 import org.keycloak.models.picketlink.relationships.RealmAdminRelationship;
+import org.keycloak.models.utils.KeycloakSessionUtils;
 import org.picketlink.idm.PartitionManager;
 import org.picketlink.idm.RelationshipManager;
 import org.picketlink.idm.query.RelationshipQuery;
@@ -25,11 +26,6 @@ public class PicketlinkKeycloakSession implements KeycloakSession {
     protected PartitionManager partitionManager;
     protected EntityManager entityManager;
 
-    private static AtomicLong counter = new AtomicLong(1);
-    public static String generateId() {
-        return counter.getAndIncrement() + "-" + System.currentTimeMillis();
-    }
-
     public PicketlinkKeycloakSession(PartitionManager partitionManager, EntityManager entityManager) {
         this.partitionManager = partitionManager;
         this.entityManager = entityManager;
@@ -50,7 +46,7 @@ public class PicketlinkKeycloakSession implements KeycloakSession {
 
     @Override
     public RealmAdapter createRealm(String name) {
-        return createRealm(generateId(), name);
+        return createRealm(KeycloakSessionUtils.generateId(), name);
     }
 
     @Override
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
index 6c62007..d5412ce 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
@@ -819,6 +819,7 @@ public class RealmAdapter implements RealmModel {
         RelationshipQuery<SocialLinkRelationship> query = getRelationshipManager().createRelationshipQuery(SocialLinkRelationship.class);
         query.setParameter(SocialLinkRelationship.SOCIAL_PROVIDER, socialLink.getSocialProvider());
         query.setParameter(SocialLinkRelationship.SOCIAL_USERNAME, socialLink.getSocialUsername());
+        query.setParameter(SocialLinkRelationship.REALM, realm.getName());
         List<SocialLinkRelationship> results = query.getResultList();
         if (results.isEmpty()) {
             return null;
@@ -850,6 +851,7 @@ public class RealmAdapter implements RealmModel {
         relationship.setUser(((UserAdapter)user).getUser());
         relationship.setSocialProvider(socialLink.getSocialProvider());
         relationship.setSocialUsername(socialLink.getSocialUsername());
+        relationship.setRealm(realm.getName());
 
         getRelationshipManager().add(relationship);
     }
@@ -860,6 +862,7 @@ public class RealmAdapter implements RealmModel {
         relationship.setUser(((UserAdapter)user).getUser());
         relationship.setSocialProvider(socialLink.getSocialProvider());
         relationship.setSocialUsername(socialLink.getSocialUsername());
+        relationship.setRealm(realm.getName());
 
         getRelationshipManager().remove(relationship);
     }
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java
index e9be9d4..da8f04f 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java
@@ -3,6 +3,7 @@ package org.keycloak.models.picketlink.relationships;
 import org.picketlink.idm.model.AbstractAttributedType;
 import org.picketlink.idm.model.Attribute;
 import org.picketlink.idm.model.Relationship;
+import org.picketlink.idm.model.annotation.AttributeProperty;
 import org.picketlink.idm.model.sample.User;
 import org.picketlink.idm.query.AttributeParameter;
 import org.picketlink.idm.query.RelationshipQueryParameter;
@@ -21,6 +22,10 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
     public static final AttributeParameter SOCIAL_PROVIDER = new AttributeParameter("socialProvider");
     public static final AttributeParameter SOCIAL_USERNAME = new AttributeParameter("socialUsername");
 
+    // realm is needed to allow searching as combination socialUsername+socialProvider may not be unique
+    // (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2")
+    public static final AttributeParameter REALM = new AttributeParameter("realm");
+
     public static final RelationshipQueryParameter USER = new RelationshipQueryParameter() {
 
         @Override
@@ -39,6 +44,7 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
         this.user = user;
     }
 
+    @AttributeProperty
     public String getSocialProvider() {
         return (String)getAttribute("socialProvider").getValue();
     }
@@ -47,6 +53,7 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
         setAttribute(new Attribute<String>("socialProvider", socialProvider));
     }
 
+    @AttributeProperty
     public String getSocialUsername() {
         return (String)getAttribute("socialUsername").getValue();
     }
@@ -54,4 +61,13 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
     public void setSocialUsername(String socialProviderUserId) {
         setAttribute(new Attribute<String>("socialUsername", socialProviderUserId));
     }
+
+    @AttributeProperty
+    public String getRealm() {
+        return (String)getAttribute("realm").getValue();
+    }
+
+    public void setRealm(String realm) {
+        setAttribute(new Attribute<String>("realm", realm));
+    }
 }

model/pom.xml 1(+1 -0)

diff --git a/model/pom.xml b/model/pom.xml
index c6a4a9e..7e2fca5 100755
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -37,5 +37,6 @@
         <module>api</module>
         <module>picketlink</module>
         <module>jpa</module>
+        <!--<module>mongo</module>-->
     </modules>
 </project>

pom.xml 85(+80 -5)

diff --git a/pom.xml b/pom.xml
index d08e744..61503c5 100755
--- a/pom.xml
+++ b/pom.xml
@@ -12,6 +12,14 @@
         <resteasy.version>3.0.4.Final</resteasy.version>
         <undertow.version>1.0.0.Beta12</undertow.version>
         <picketlink.version>2.5.0.Beta6</picketlink.version>
+        <mongo.driver.version>2.11.2</mongo.driver.version>
+        <jboss.logging.version>3.1.1.GA</jboss.logging.version>
+        <hibernate.javax.persistence.version>1.0.1.Final</hibernate.javax.persistence.version>
+        <hibernate.entitymanager.version>3.6.6.Final</hibernate.entitymanager.version>
+        <h2.version>1.3.161</h2.version>
+        <dom4j.version>1.6.1</dom4j.version>
+        <mysql.version>5.1.25</mysql.version>
+        <slf4j.version>1.6.1</slf4j.version>
     </properties>
 
     <url>http://keycloak.org</url>
@@ -150,6 +158,11 @@
                 <version>1.0.1.Final</version>
             </dependency>
             <dependency>
+                <groupId>org.jboss.spec.javax.servlet</groupId>
+                <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+                <version>1.0.0.Beta1</version>
+            </dependency>
+            <dependency>
                 <groupId>org.picketlink</groupId>
                 <artifactId>picketlink-common</artifactId>
                 <version>${picketlink.version}</version>
@@ -177,7 +190,7 @@
             <dependency>
                 <groupId>org.jboss.logging</groupId>
                 <artifactId>jboss-logging</artifactId>
-                <version>3.1.1.GA</version>
+                <version>${jboss.logging.version}</version>
             </dependency>
             <dependency>
                 <groupId>junit</groupId>
@@ -187,7 +200,17 @@
             <dependency>
                 <groupId>org.hibernate.javax.persistence</groupId>
                 <artifactId>hibernate-jpa-2.0-api</artifactId>
-                <version>1.0.1.Final</version>
+                <version>${hibernate.javax.persistence.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.h2database</groupId>
+                <artifactId>h2</artifactId>
+                <version>${h2.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.hibernate</groupId>
+                <artifactId>hibernate-entitymanager</artifactId>
+                <version>${hibernate.entitymanager.version}</version>
             </dependency>
             <dependency>
                 <groupId>com.google.api-client</groupId>
@@ -236,9 +259,9 @@
     			<groupId>com.icegreen</groupId>
     			<artifactId>greenmail</artifactId>
 	    		<version>1.3.1b</version>
-			</dependency>  			
+			</dependency>
 
-			<!-- Selenium -->				
+			<!-- Selenium -->
 			<dependency>
 				<groupId>org.seleniumhq.selenium</groupId>
 				<artifactId>selenium-java</artifactId>
@@ -248,7 +271,39 @@
 				<groupId>org.seleniumhq.selenium</groupId>
 				<artifactId>selenium-chrome-driver</artifactId>
 				<version>2.35.0</version>
-			</dependency>  			
+			</dependency>
+            <dependency>
+                <groupId>org.mongodb</groupId>
+                <artifactId>mongo-java-driver</artifactId>
+                <version>2.11.2</version>
+            </dependency>
+            <dependency>
+                <groupId>de.flapdoodle.embed</groupId>
+                <artifactId>de.flapdoodle.embed.mongo</artifactId>
+                <version>1.27</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.jmeter</groupId>
+                <artifactId>ApacheJMeter_java</artifactId>
+                <version>2.9</version>
+            </dependency>
+            <dependency>
+                <groupId>dom4j</groupId>
+                <artifactId>dom4j</artifactId>
+                <version>${dom4j.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-api</artifactId>
+                <version>${slf4j.version}</version>
+            </dependency>
+            <!-- Needed for picketlink perf test -->
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>${mysql.version}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>
 
@@ -330,6 +385,26 @@
 						<failOnMissingWebXml>false</failOnMissingWebXml>
 					</configuration>
 				</plugin>
+                <plugin>
+                    <groupId>com.lazerycode.jmeter</groupId>
+                    <artifactId>jmeter-maven-plugin</artifactId>
+                    <version>1.8.1</version>
+                </plugin>
+                <plugin>
+                    <groupId>com.lazerycode.jmeter</groupId>
+                    <artifactId>jmeter-analysis-maven-plugin</artifactId>
+                    <version>1.0.4</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-jar-plugin</artifactId>
+                    <version>2.2</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>exec-maven-plugin</artifactId>
+                    <version>1.2.1</version>
+                </plugin>
             </plugins>
         </pluginManagement>
 

services/pom.xml 23(+21 -2)

diff --git a/services/pom.xml b/services/pom.xml
index 4d8075f..187d7be 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -34,6 +34,12 @@
             <artifactId>keycloak-model-picketlink</artifactId>
             <version>${project.version}</version>
         </dependency>
+
+        <!--<dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-mongo</artifactId>
+            <version>${project.version}</version>
+        </dependency>-->
         <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-social-core</artifactId>
@@ -145,6 +151,21 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-common</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mongodb</groupId>
+            <artifactId>mongo-java-driver</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>de.flapdoodle.embed</groupId>
+            <artifactId>de.flapdoodle.embed.mongo</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
@@ -157,13 +178,11 @@
         <dependency>
             <groupId>com.h2database</groupId>
             <artifactId>h2</artifactId>
-            <version>1.3.161</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.hibernate</groupId>
             <artifactId>hibernate-entitymanager</artifactId>
-            <version>3.6.6.Final</version>
             <scope>test</scope>
         </dependency>
 		<dependency>
diff --git a/services/src/main/java/org/keycloak/services/listeners/MongoRunnerListener.java b/services/src/main/java/org/keycloak/services/listeners/MongoRunnerListener.java
new file mode 100644
index 0000000..f0df0a6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/listeners/MongoRunnerListener.java
@@ -0,0 +1,53 @@
+package org.keycloak.services.listeners;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import de.flapdoodle.embed.mongo.MongodExecutable;
+import de.flapdoodle.embed.mongo.MongodProcess;
+import de.flapdoodle.embed.mongo.MongodStarter;
+import de.flapdoodle.embed.mongo.config.MongodConfig;
+import de.flapdoodle.embed.mongo.distribution.Version;
+import de.flapdoodle.embed.process.runtime.Network;
+import org.jboss.resteasy.logging.Logger;
+import org.keycloak.services.utils.PropertiesManager;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MongoRunnerListener implements ServletContextListener {
+
+    protected static final Logger logger = Logger.getLogger(MongoRunnerListener.class);
+
+    private MongodExecutable mongodExe;
+    private MongodProcess mongod;
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce) {
+        if (PropertiesManager.bootstrapEmbeddedMongoAtContextInit()) {
+            int port = PropertiesManager.getMongoPort();
+            logger.info("Going to start embedded MongoDB on port=" + port);
+
+            try {
+                mongodExe = MongodStarter.getDefaultInstance().prepare(new MongodConfig(Version.V2_0_5, port, Network.localhostIsIPv6()));
+                mongod = mongodExe.start();
+            } catch (Exception e) {
+                logger.warn("Couldn't start Embedded Mongo on port " + port + ". Maybe it's already started? Cause: " + e.getClass() + " " + e.getMessage());
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Failed to start MongoDB", e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent sce) {
+        if (mongodExe != null) {
+            if (mongod != null) {
+                logger.info("Going to stop embedded MongoDB.");
+                mongod.stop();
+            }
+            mongodExe.stop();
+        }
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
index cdb588e..133b062 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
@@ -125,7 +125,7 @@ public class FormFlows {
 
         // TODO find a better way to obtain contextPath
         // Getting context path by removing "rest/" substring from the BaseUri path
-        formDataBean.setContextPath(requestURI.substring(0,requestURI.length()-5));
+        formDataBean.setContextPath(requestURI.substring(0, requestURI.length() - 6));
         formDataBean.setSocialRegistration(socialRegistration);
 
         // Find the service and process relevant template
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 49855b9..0340751 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -7,6 +7,7 @@ import org.keycloak.models.picketlink.PicketlinkKeycloakSession;
 import org.keycloak.models.picketlink.PicketlinkKeycloakSessionFactory;
 import org.keycloak.models.picketlink.mappings.ApplicationEntity;
 import org.keycloak.models.picketlink.mappings.RealmEntity;
+import org.keycloak.services.utils.PropertiesManager;
 import org.keycloak.social.SocialRequestManager;
 import org.picketlink.idm.PartitionManager;
 import org.picketlink.idm.config.IdentityConfigurationBuilder;
@@ -21,6 +22,8 @@ import javax.persistence.Persistence;
 import javax.servlet.ServletContext;
 import javax.ws.rs.core.Application;
 import javax.ws.rs.core.Context;
+
+import java.lang.reflect.Constructor;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -29,6 +32,7 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public class KeycloakApplication extends Application {
+
     protected Set<Object> singletons = new HashSet<Object>();
     protected Set<Class<?>> classes = new HashSet<Class<?>>();
 
@@ -54,10 +58,36 @@ public class KeycloakApplication extends Application {
     }
 
     public static KeycloakSessionFactory buildSessionFactory() {
+        if (PropertiesManager.isMongoSessionFactory()) {
+            return buildMongoDBSessionFactory();
+        } else if (PropertiesManager.isPicketlinkSessionFactory()) {
+            return buildPicketlinkSessionFactory();
+        } else {
+            throw new IllegalStateException("Unknown session factory type: " + PropertiesManager.getSessionFactoryType());
+        }
+    }
+
+    private static KeycloakSessionFactory buildPicketlinkSessionFactory() {
         EntityManagerFactory emf = Persistence.createEntityManagerFactory("keycloak-identity-store");
         return new PicketlinkKeycloakSessionFactory(emf, buildPartitionManager());
     }
 
+    private static KeycloakSessionFactory buildMongoDBSessionFactory() {
+        String host = PropertiesManager.getMongoHost();
+        int port = PropertiesManager.getMongoPort();
+        String dbName = PropertiesManager.getMongoDbName();
+        boolean dropDatabaseOnStartup = PropertiesManager.dropDatabaseOnStartup();
+
+        // Create MongoDBSessionFactory via reflection now
+        try {
+            Class<? extends KeycloakSessionFactory> mongoDBSessionFactoryClass = (Class<? extends KeycloakSessionFactory>)Class.forName("org.keycloak.models.mongo.keycloak.adapters.MongoDBSessionFactory");
+            Constructor<? extends KeycloakSessionFactory> constr = mongoDBSessionFactoryClass.getConstructor(String.class, int.class, String.class, boolean.class);
+            return constr.newInstance(host, port, dbName, dropDatabaseOnStartup);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     public KeycloakSessionFactory getFactory() {
         return factory;
     }
diff --git a/services/src/main/java/org/keycloak/services/utils/PropertiesManager.java b/services/src/main/java/org/keycloak/services/utils/PropertiesManager.java
new file mode 100644
index 0000000..ee16547
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/utils/PropertiesManager.java
@@ -0,0 +1,82 @@
+package org.keycloak.services.utils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PropertiesManager {
+
+    private static final String SESSION_FACTORY = "keycloak.sessionFactory";
+    public static final String SESSION_FACTORY_PICKETLINK = "picketlink";
+    public static final String SESSION_FACTORY_MONGO = "mongo";
+
+    private static final String MONGO_HOST = "keycloak.mongodb.host";
+    private static final String MONGO_PORT = "keycloak.mongodb.port";
+    private static final String MONGO_DB_NAME = "keycloak.mongodb.databaseName";
+    private static final String MONGO_DROP_DB_ON_STARTUP = "keycloak.mongodb.dropDatabaseOnStartup";
+    private static final String BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT = "keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit";
+
+    // Port where embedded MongoDB will be started during keycloak bootstrap. Same port will be used by KeycloakApplication then
+    private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED = 37017;
+
+    // Port where MongoDB instance is normally started on linux. This port should be used if we're not starting embedded instance (keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit is false)
+    private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR = 27017;
+
+    // Port where unit tests will start embedded MongoDB instance
+    public static final int MONGO_DEFAULT_PORT_UNIT_TESTS = 27777;
+
+    public static String getSessionFactoryType() {
+        return System.getProperty(SESSION_FACTORY, SESSION_FACTORY_PICKETLINK);
+    }
+
+    public static void setSessionFactoryType(String sessionFactoryType) {
+        System.setProperty(SESSION_FACTORY, sessionFactoryType);
+    }
+
+    public static void setDefaultSessionFactoryType() {
+        System.setProperty(SESSION_FACTORY, SESSION_FACTORY_PICKETLINK);
+    }
+
+    public static boolean isMongoSessionFactory() {
+        return getSessionFactoryType().equals(SESSION_FACTORY_MONGO);
+    }
+
+    public static boolean isPicketlinkSessionFactory() {
+        return getSessionFactoryType().equals(SESSION_FACTORY_PICKETLINK);
+    }
+
+    public static String getMongoHost() {
+        return System.getProperty(MONGO_HOST, "localhost");
+    }
+
+    public static void setMongoHost(String mongoHost) {
+        System.setProperty(MONGO_HOST, mongoHost);
+    }
+
+    public static int getMongoPort() {
+        return Integer.parseInt(System.getProperty(MONGO_PORT, String.valueOf(MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED)));
+    }
+
+    public static void setMongoPort(int mongoPort) {
+        System.setProperty(MONGO_PORT, String.valueOf(mongoPort));
+    }
+
+    public static String getMongoDbName() {
+        return System.getProperty(MONGO_DB_NAME, "keycloak");
+    }
+
+    public static void setMongoDbName(String mongoMongoDbName) {
+        System.setProperty(MONGO_DB_NAME, mongoMongoDbName);
+    }
+
+    public static boolean dropDatabaseOnStartup() {
+        return Boolean.parseBoolean(System.getProperty(MONGO_DROP_DB_ON_STARTUP, "true"));
+    }
+
+    public static void setDropDatabaseOnStartup(boolean dropDatabaseOnStartup) {
+        System.setProperty(MONGO_DROP_DB_ON_STARTUP, String.valueOf(dropDatabaseOnStartup));
+    }
+
+    public static boolean bootstrapEmbeddedMongoAtContextInit() {
+        return isMongoSessionFactory() && Boolean.parseBoolean(System.getProperty(BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT, "true"));
+    }
+}
diff --git a/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java b/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java
index ce3ed3d..a2049fd 100755
--- a/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java
+++ b/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java
@@ -18,24 +18,20 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.services.resources.KeycloakApplication;
+import org.keycloak.test.common.AbstractKeycloakTest;
+import org.keycloak.test.common.SessionFactoryTestContext;
 import org.picketlink.idm.credential.util.TimeBasedOTP;
 
-public class AuthenticationManagerTest {
+public class AuthenticationManagerTest extends AbstractKeycloakTest {
 
-    private RealmManager adapter;
     private AuthenticationManager am;
-    private KeycloakSessionFactory factory;
     private MultivaluedMap<String, String> formData;
-    private KeycloakSession identitySession;
     private TimeBasedOTP otp;
     private RealmModel realm;
     private UserModel user;
 
-    @After
-    public void after() throws Exception {
-        identitySession.getTransaction().commit();
-        identitySession.close();
-        factory.close();
+    public AuthenticationManagerTest(SessionFactoryTestContext testContext) {
+        super(testContext);
     }
 
     @Test
@@ -134,12 +130,8 @@ public class AuthenticationManagerTest {
 
     @Before
     public void before() throws Exception {
-        factory = KeycloakApplication.buildSessionFactory();
-        identitySession = factory.createSession();
-        identitySession.getTransaction().begin();
-        adapter = new RealmManager(identitySession);
-
-        realm = adapter.createRealm("Test");
+        super.before();
+        realm = getRealmManager().createRealm("Test");
         realm.setAccessCodeLifespan(100);
         realm.setCookieLoginAllowed(true);
         realm.setEnabled(true);
diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java
index 1bdaa82..41aa8e1 100755
--- a/services/src/test/java/org/keycloak/test/AdapterTest.java
+++ b/services/src/test/java/org/keycloak/test/AdapterTest.java
@@ -12,6 +12,8 @@ import org.keycloak.services.managers.OAuthClientManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.services.resources.KeycloakApplication;
+import org.keycloak.test.common.AbstractKeycloakTest;
+import org.keycloak.test.common.SessionFactoryTestContext;
 
 
 import java.util.HashSet;
@@ -24,30 +26,16 @@ import java.util.StringTokenizer;
  * @version $Revision: 1 $
  */
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class AdapterTest {
-    private KeycloakSessionFactory factory;
-    private KeycloakSession identitySession;
-    private RealmManager adapter;
+public class AdapterTest extends AbstractKeycloakTest {
     private RealmModel realmModel;
 
-    @Before
-    public void before() throws Exception {
-        factory = KeycloakApplication.buildSessionFactory();
-        identitySession = factory.createSession();
-        identitySession.getTransaction().begin();
-        adapter = new RealmManager(identitySession);
-    }
-
-    @After
-    public void after() throws Exception {
-        identitySession.getTransaction().commit();
-        identitySession.close();
-        factory.close();
+    public AdapterTest(SessionFactoryTestContext testContext) {
+        super(testContext);
     }
 
     @Test
     public void installTest() throws Exception {
-        new InstallationManager().install(adapter);
+        new InstallationManager().install(getRealmManager());
 
     }
 
@@ -63,7 +51,7 @@ public class AdapterTest {
 
     @Test
     public void test1CreateRealm() throws Exception {
-        realmModel = adapter.createRealm("JUGGLER");
+        realmModel = getRealmManager().createRealm("JUGGLER");
         realmModel.setAccessCodeLifespan(100);
         realmModel.setAccessCodeLifespanUserAction(600);
         realmModel.setCookieLoginAllowed(true);
@@ -76,7 +64,7 @@ public class AdapterTest {
         realmModel.addDefaultRole("foo");
 
         System.out.println(realmModel.getId());
-        realmModel = adapter.getRealm(realmModel.getId());
+        realmModel = getRealmManager().getRealm(realmModel.getId());
         Assert.assertNotNull(realmModel);
         Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100);
         Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction());
@@ -153,6 +141,8 @@ public class AdapterTest {
             user.setEmail("bburke@redhat.com");
         }
 
+        RealmManager adapter = getRealmManager();
+
         {
             List<UserModel> userModels = adapter.searchUsers("total junk query", realmModel);
             Assert.assertEquals(userModels.size(), 0);
diff --git a/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java b/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java
new file mode 100644
index 0000000..baaa9f6
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java
@@ -0,0 +1,96 @@
+package org.keycloak.test.common;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.resources.KeycloakApplication;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@RunWith(Parameterized.class)
+public abstract class AbstractKeycloakTest {
+
+    protected static final SessionFactoryTestContext[] TEST_CONTEXTS;
+
+    private final SessionFactoryTestContext testContext;
+    private KeycloakSessionFactory factory;
+    private KeycloakSession identitySession;
+    private RealmManager realmManager;
+
+    // STATIC METHODS
+
+    static
+    {
+        // TODO: MongoDB disabled by default
+        TEST_CONTEXTS = new SessionFactoryTestContext[] {
+                new PicketlinkSessionFactoryTestContext(),
+                // new MongoDBSessionFactoryTestContext()
+        };
+    }
+
+    @Parameterized.Parameters
+    public static Iterable<Object[]> parameters() {
+        List<Object[]> params = new ArrayList<Object[]>();
+
+        for (SessionFactoryTestContext testContext : TEST_CONTEXTS) {
+            params.add(new Object[] {testContext});
+        }
+        return params;
+    }
+
+    @BeforeClass
+    public static void baseBeforeClass() {
+        for (SessionFactoryTestContext testContext : TEST_CONTEXTS) {
+            testContext.beforeTestClass();
+        }
+    }
+
+    @AfterClass
+    public static void baseAfterClass() {
+        for (SessionFactoryTestContext testContext : TEST_CONTEXTS) {
+            testContext.afterTestClass();
+        }
+    }
+
+    // NON-STATIC METHODS
+
+    public AbstractKeycloakTest(SessionFactoryTestContext testContext) {
+        this.testContext = testContext;
+    }
+
+    @Before
+    public void before() throws Exception {
+        testContext.initEnvironment();
+        factory = KeycloakApplication.buildSessionFactory();
+        identitySession = factory.createSession();
+        identitySession.getTransaction().begin();
+        realmManager = new RealmManager(identitySession);
+    }
+
+    @After
+    public void after() throws Exception {
+        identitySession.getTransaction().commit();
+        identitySession.close();
+        factory.close();
+    }
+
+    protected RealmManager getRealmManager() {
+        return realmManager;
+    }
+
+    protected KeycloakSession getIdentitySession() {
+        return identitySession;
+    }
+
+}
diff --git a/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java
new file mode 100644
index 0000000..def3f34
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java
@@ -0,0 +1,58 @@
+package org.keycloak.test.common;
+
+import de.flapdoodle.embed.mongo.MongodExecutable;
+import de.flapdoodle.embed.mongo.MongodProcess;
+import de.flapdoodle.embed.mongo.MongodStarter;
+import de.flapdoodle.embed.mongo.config.MongodConfig;
+import de.flapdoodle.embed.mongo.distribution.Version;
+import de.flapdoodle.embed.process.runtime.Network;
+import org.jboss.resteasy.logging.Logger;
+import org.keycloak.services.resources.KeycloakApplication;
+import org.keycloak.services.utils.PropertiesManager;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MongoDBSessionFactoryTestContext implements SessionFactoryTestContext {
+
+    protected static final Logger logger = Logger.getLogger(MongoDBSessionFactoryTestContext.class);
+    private static final int PORT = PropertiesManager.MONGO_DEFAULT_PORT_UNIT_TESTS;
+
+    private MongodExecutable mongodExe;
+    private MongodProcess mongod;
+
+    @Override
+    public void beforeTestClass() {
+        logger.info("Bootstrapping MongoDB on localhost, port " + PORT);
+        try {
+            mongodExe = MongodStarter.getDefaultInstance().prepare(new MongodConfig(Version.V2_0_5, PORT, Network.localhostIsIPv6()));
+            mongod = mongodExe.start();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        logger.info("MongoDB bootstrapped successfully");
+    }
+
+    @Override
+    public void afterTestClass() {
+        if (mongodExe != null) {
+            if (mongod != null) {
+                mongod.stop();
+            }
+            mongodExe.stop();
+        }
+        logger.info("MongoDB stopped successfully");
+
+        // Reset this, so other tests are not affected
+        PropertiesManager.setDefaultSessionFactoryType();
+    }
+
+    @Override
+    public void initEnvironment() {
+        PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_MONGO);
+        PropertiesManager.setMongoHost("localhost");
+        PropertiesManager.setMongoPort(PORT);
+        PropertiesManager.setMongoDbName("keycloakTest");
+        PropertiesManager.setDropDatabaseOnStartup(true);
+    }
+}
diff --git a/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java
new file mode 100644
index 0000000..80b2cbc
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java
@@ -0,0 +1,25 @@
+package org.keycloak.test.common;
+
+import org.keycloak.services.resources.KeycloakApplication;
+import org.keycloak.services.utils.PropertiesManager;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PicketlinkSessionFactoryTestContext implements SessionFactoryTestContext {
+
+    @Override
+    public void beforeTestClass() {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void afterTestClass() {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void initEnvironment() {
+        PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_PICKETLINK);
+    }
+}
diff --git a/services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java
new file mode 100644
index 0000000..a35cfd2
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java
@@ -0,0 +1,17 @@
+package org.keycloak.test.common;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface SessionFactoryTestContext {
+
+    void beforeTestClass();
+
+    void afterTestClass();
+
+    /**
+     * Init system properties (or other configuration) to ensure that KeycloakApplication.buildSessionFactory() will return correct
+     * instance of KeycloakSessionFactory for our test
+     */
+    void initEnvironment();
+}
diff --git a/services/src/test/java/org/keycloak/test/ImportTest.java b/services/src/test/java/org/keycloak/test/ImportTest.java
index d426d4e..33348ed 100755
--- a/services/src/test/java/org/keycloak/test/ImportTest.java
+++ b/services/src/test/java/org/keycloak/test/ImportTest.java
@@ -19,6 +19,8 @@ import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.services.resources.KeycloakApplication;
 import org.keycloak.services.resources.SaasService;
+import org.keycloak.test.common.AbstractKeycloakTest;
+import org.keycloak.test.common.SessionFactoryTestContext;
 
 import java.util.List;
 import java.util.Set;
@@ -28,29 +30,15 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class ImportTest {
-    private KeycloakSessionFactory factory;
-    private KeycloakSession identitySession;
-    private RealmManager manager;
-    private RealmModel realmModel;
-
-    @Before
-    public void before() throws Exception {
-        factory = KeycloakApplication.buildSessionFactory();
-        identitySession = factory.createSession();
-        identitySession.getTransaction().begin();
-        manager = new RealmManager(identitySession);
-    }
+public class ImportTest extends AbstractKeycloakTest {
 
-    @After
-    public void after() throws Exception {
-        identitySession.getTransaction().commit();
-        identitySession.close();
-        factory.close();
+    public ImportTest(SessionFactoryTestContext testContext) {
+        super(testContext);
     }
 
     @Test
     public void install() throws Exception {
+        RealmManager manager = getRealmManager();
         RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM);
         defaultRealm.setName(RealmModel.DEFAULT_REALM);
         defaultRealm.setEnabled(true);
@@ -93,7 +81,7 @@ public class ImportTest {
 
         List<ApplicationModel> resources = realm.getApplications();
         Assert.assertEquals(2, resources.size());
-        List<RealmModel> realms = identitySession.getRealms(admin);
+        List<RealmModel> realms = getIdentitySession().getRealms(admin);
         Assert.assertEquals(1, realms.size());
 
         // Test scope relationship
@@ -129,6 +117,7 @@ public class ImportTest {
 
     @Test
     public void install2() throws Exception {
+        RealmManager manager = getRealmManager();
         RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM);
         defaultRealm.setName(RealmModel.DEFAULT_REALM);
         defaultRealm.setEnabled(true);
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
new file mode 100644
index 0000000..f0f3eea
--- /dev/null
+++ b/testsuite/integration/pom.xml
@@ -0,0 +1,253 @@
+<?xml version="1.0"?>
+<project>
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-alpha-1</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-testsuite-integration</artifactId>
+    <name>Keycloak Integration TestSuite</name>
+    <description />
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-as7-adapter</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk16</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-admin-ui</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-admin-ui-styles</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-services</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-social-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-social-google</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-social-twitter</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-social-facebook</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-forms</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-idm-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-idm-impl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-idm-simple-schema</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-config</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>log4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-simple</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>jaxrs-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-crypto</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>jose-jwt</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-undertow</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.undertow</groupId>
+            <artifactId>undertow-servlet</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.undertow</groupId>
+            <artifactId>undertow-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-xc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate.javax.persistence</groupId>
+            <artifactId>hibernate-jpa-2.0-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <version>1.3.161</version>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-entitymanager</artifactId>
+            <version>3.6.6.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>com.icegreen</groupId>
+            <artifactId>greenmail</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.seleniumhq.selenium</groupId>
+            <artifactId>selenium-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mongodb</groupId>
+            <artifactId>mongo-java-driver</artifactId>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>keycloak-server</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.testutils.KeycloakServer</mainClass>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>mail-server</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.testutils.MailServer</mainClass>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>totp</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.testutils.TotpGenerator</mainClass>
+                            <arguments>
+                                <argument>${secret}</argument>
+                            </arguments>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
\ No newline at end of file
diff --git a/testsuite/performance/pom.xml b/testsuite/performance/pom.xml
new file mode 100644
index 0000000..1cb8220
--- /dev/null
+++ b/testsuite/performance/pom.xml
@@ -0,0 +1,240 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-alpha-1</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-testsuite-performance</artifactId>
+    <name>Keycloak Performance TestSuite</name>
+    <description />
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-services</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+            <scope>provided</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>log4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-simple</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>jaxrs-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-client</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jmeter</groupId>
+            <artifactId>ApacheJMeter_java</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>performance-tests</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>com.lazerycode.jmeter</groupId>
+                        <artifactId>jmeter-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>jmeter-tests</id>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>jmeter</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                        <dependencies>
+                            <dependency>
+                                <groupId>org.keycloak</groupId>
+                                <artifactId>keycloak-testsuite</artifactId>
+                                <version>${project.version}</version>
+                                <type>test-jar</type>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.keycloak</groupId>
+                                <artifactId>keycloak-services</artifactId>
+                                <version>${project.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.jboss.resteasy</groupId>
+                                <artifactId>jaxrs-api</artifactId>
+                                <version>${resteasy.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.jboss.resteasy</groupId>
+                                <artifactId>resteasy-jaxrs</artifactId>
+                                <version>${resteasy.version}</version>
+                                <exclusions>
+                                    <exclusion>
+                                        <groupId>log4j</groupId>
+                                        <artifactId>log4j</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>org.slf4j</groupId>
+                                        <artifactId>slf4j-api</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>org.slf4j</groupId>
+                                        <artifactId>slf4j-simple</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>commons-io</groupId>
+                                        <artifactId>commons-io</artifactId>
+                                    </exclusion>
+                                </exclusions>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.jboss.logging</groupId>
+                                <artifactId>jboss-logging</artifactId>
+                                <version>${jboss.logging.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.picketlink</groupId>
+                                <artifactId>picketlink-idm-impl</artifactId>
+                                <version>${picketlink.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.picketlink</groupId>
+                                <artifactId>picketlink-idm-simple-schema</artifactId>
+                                <version>${picketlink.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.picketlink</groupId>
+                                <artifactId>picketlink-config</artifactId>
+                                <version>${picketlink.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.mongodb</groupId>
+                                <artifactId>mongo-java-driver</artifactId>
+                                <version>${mongo.driver.version}</version>
+                            </dependency>
+
+                            <!-- Needed for picketlink -->
+                            <dependency>
+                                <groupId>org.hibernate.javax.persistence</groupId>
+                                <artifactId>hibernate-jpa-2.0-api</artifactId>
+                                <version>${hibernate.javax.persistence.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>com.h2database</groupId>
+                                <artifactId>h2</artifactId>
+                                <version>${h2.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.hibernate</groupId>
+                                <artifactId>hibernate-entitymanager</artifactId>
+                                <version>${hibernate.entitymanager.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>dom4j</groupId>
+                                <artifactId>dom4j</artifactId>
+                                <version>${dom4j.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.slf4j</groupId>
+                                <artifactId>slf4j-api</artifactId>
+                                <version>${slf4j.version}</version>
+                            </dependency>
+
+                            <!-- Needed just for picketlink perf test -->
+                            <dependency>
+                                <groupId>mysql</groupId>
+                                <artifactId>mysql-connector-java</artifactId>
+                                <version>${mysql.version}</version>
+                            </dependency>
+
+                        </dependencies>
+                    </plugin>
+
+                    <plugin>
+                        <groupId>com.lazerycode.jmeter</groupId>
+                        <artifactId>jmeter-analysis-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>jmeter-tests-analyze</id>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>analyze</goal>
+                                </goals>
+                                <configuration>
+                                    <source>${project.build.directory}/jmeter/results/*.jtl</source>
+                                    <targetDirectory>${project.build.directory}/jmeter/results</targetDirectory>
+                                    <preserveDirectories>false</preserveDirectories>
+                                    <writers>
+                                        <com.lazerycode.jmeter.analyzer.writer.SummaryTextToStdOutWriter/>
+                                        <!--<com.lazerycode.jmeter.analyzer.writer.SummaryTextToFileWriter/>-->
+                                        <com.lazerycode.jmeter.analyzer.writer.HtmlWriter/>
+                                        <!--<com.lazerycode.jmeter.analyzer.writer.DetailsToCsvWriter/>-->
+                                        <com.lazerycode.jmeter.analyzer.writer.DetailsToHtmlWriter/>
+                                        <com.lazerycode.jmeter.analyzer.writer.ChartWriter/>
+                                    </writers>
+
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                </plugins>
+            </build>
+        </profile>
+
+    </profiles>
+</project>
\ No newline at end of file
diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java
new file mode 100644
index 0000000..efe57c1
--- /dev/null
+++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java
@@ -0,0 +1,140 @@
+package org.keycloak.testsuite.performance;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
+import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
+import org.apache.jmeter.samplers.SampleResult;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.services.resources.KeycloakApplication;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class BaseJMeterPerformanceTest extends AbstractJavaSamplerClient {
+
+
+    private static FutureTask<KeycloakSessionFactory> factoryProvider = new FutureTask<KeycloakSessionFactory>(new Callable() {
+
+        @Override
+        public KeycloakSessionFactory call() throws Exception {
+            return KeycloakApplication.buildSessionFactory();
+        }
+
+    });
+    private static AtomicInteger counter = new AtomicInteger();
+
+    private KeycloakSessionFactory factory;
+    // private KeycloakSession identitySession;
+    private Worker worker;
+    private boolean setupSuccess = false;
+
+
+    // Executed once per JMeter thread
+    @Override
+    public void setupTest(JavaSamplerContext context) {
+        super.setupTest(context);
+
+        worker = getWorker();
+
+        factory = getFactory();
+        KeycloakSession identitySession = factory.createSession();
+        KeycloakTransaction transaction = identitySession.getTransaction();
+        transaction.begin();
+
+        int workerId = counter.getAndIncrement();
+        try {
+            worker.setup(workerId, identitySession);
+            setupSuccess = true;
+        } finally {
+            if (setupSuccess) {
+                transaction.commit();
+            } else {
+                transaction.rollback();
+            }
+            identitySession.close();
+        }
+    }
+
+    private static KeycloakSessionFactory getFactory() {
+        factoryProvider.run();
+        try {
+            return factoryProvider.get();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    private Worker getWorker() {
+        String workerClass = System.getProperty("keycloak.perf.workerClass");
+        if (workerClass == null) {
+            throw new IllegalArgumentException("System property keycloak.perf.workerClass needs to be provided");
+        }
+
+        try {
+            Class workerClazz = Class.forName(workerClass);
+            return (Worker)workerClazz.newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    @Override
+    public SampleResult runTest(JavaSamplerContext context) {
+        SampleResult result = new SampleResult();
+        result.sampleStart();
+
+        if (!setupSuccess) {
+            getLogger().error("setupTest didn't executed successfully. Skipping");
+            result.setResponseCode("500");
+            result.sampleEnd();
+            result.setSuccessful(true);
+            return result;
+        }
+
+        KeycloakSession identitySession = factory.createSession();
+        KeycloakTransaction transaction = identitySession.getTransaction();
+        try {
+            transaction.begin();
+
+            worker.run(result, identitySession);
+
+            result.setResponseCodeOK();
+            transaction.commit();
+        } catch (Exception e) {
+            getLogger().error("Error during worker processing", e);
+            result.setResponseCode("500");
+            transaction.rollback();
+        } finally {
+            result.sampleEnd();
+            result.setSuccessful(true);
+            identitySession.close();
+        }
+
+        return result;
+    }
+
+
+    // Executed once per JMeter thread
+    @Override
+    public void teardownTest(JavaSamplerContext context) {
+        super.teardownTest(context);
+
+        if (worker != null) {
+            worker.tearDown();
+        }
+
+        // TODO: Assumption is that tearDownTest is executed for each setupTest. Verify if it's always true...
+        if (counter.decrementAndGet() == 0) {
+            if (factory != null) {
+                factory.close();
+            }
+        }
+    }
+}
diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java
new file mode 100644
index 0000000..2749998
--- /dev/null
+++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java
@@ -0,0 +1,101 @@
+package org.keycloak.testsuite.performance;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.RealmManager;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CreateRealmsWorker implements Worker {
+
+    private static final Logger log = LoggingManager.getLoggerForClass();
+
+    private static final int NUMBER_OF_REALMS_IN_EACH_REPORT = 100;
+
+    private static AtomicInteger realmCounter = new AtomicInteger(0);
+
+    private int offset;
+    private int appsPerRealm;
+    private int rolesPerRealm;
+    private int defaultRolesPerRealm;
+    private int rolesPerApp;
+    private boolean createRequiredCredentials;
+
+    @Override
+    public void setup(int workerId, KeycloakSession identitySession) {
+        offset = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.realms.offset", Integer.class);
+        appsPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.appsPerRealm", Integer.class);
+        rolesPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.rolesPerRealm", Integer.class);
+        defaultRolesPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.defaultRolesPerRealm", Integer.class);
+        rolesPerApp = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.rolesPerApp", Integer.class);
+        createRequiredCredentials = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.createRequiredCredentials", Boolean.class);
+
+        realmCounter.compareAndSet(0, offset);
+
+        StringBuilder logBuilder = new StringBuilder("Read setup: ")
+                .append("offset=" + offset)
+                .append(", appsPerRealm=" + appsPerRealm)
+                .append(", rolesPerRealm=" + rolesPerRealm)
+                .append(", defaultRolesPerRealm=" + defaultRolesPerRealm)
+                .append(", rolesPerApp=" + rolesPerApp)
+                .append(", createRequiredCredentials=" + createRequiredCredentials);
+        log.info(logBuilder.toString());
+    }
+
+    @Override
+    public void run(SampleResult result, KeycloakSession identitySession) {
+        int realmNumber = realmCounter.getAndIncrement();
+        String realmName = PerfTestUtils.getRealmName(realmNumber);
+        RealmManager realmManager = new RealmManager(identitySession);
+        RealmModel realm = realmManager.createRealm(realmName, realmName);
+
+        // Add roles
+        for (int i=1 ; i<=rolesPerRealm ; i++) {
+            realm.addRole(PerfTestUtils.getRoleName(realmNumber, i));
+        }
+
+        // Add default roles
+        for (int i=1 ; i<=defaultRolesPerRealm ; i++) {
+            realm.addDefaultRole(PerfTestUtils.getDefaultRoleName(realmNumber, i));
+        }
+
+        // Add applications
+        for (int i=1 ; i<=appsPerRealm ; i++) {
+            ApplicationModel application = realm.addApplication(PerfTestUtils.getApplicationName(realmNumber, i));
+            for (int j=1 ; j<=rolesPerApp ; j++) {
+                application.addRole(PerfTestUtils.getApplicationRoleName(realmNumber, i, j));
+            }
+        }
+
+        // Add required credentials
+        if (createRequiredCredentials) {
+            realmManager.addRequiredCredential(realm, CredentialRepresentation.PASSWORD);
+            realmManager.addResourceRequiredCredential(realm, CredentialRepresentation.PASSWORD);
+            realmManager.addOAuthClientRequiredCredential(realm, CredentialRepresentation.PASSWORD);
+            realmManager.addRequiredCredential(realm, CredentialRepresentation.TOTP);
+            realmManager.addResourceRequiredCredential(realm, CredentialRepresentation.TOTP);
+            realmManager.addOAuthClientRequiredCredential(realm, CredentialRepresentation.TOTP);
+            realmManager.addRequiredCredential(realm, CredentialRepresentation.CLIENT_CERT);
+            realmManager.addResourceRequiredCredential(realm, CredentialRepresentation.CLIENT_CERT);
+            realmManager.addOAuthClientRequiredCredential(realm, CredentialRepresentation.CLIENT_CERT);
+        }
+
+        log.info("Finished creation of realm " + realmName);
+
+        int labelC = ((realmNumber - 1) / NUMBER_OF_REALMS_IN_EACH_REPORT) * NUMBER_OF_REALMS_IN_EACH_REPORT;
+        result.setSampleLabel("CreateRealms " + (labelC + 1) + "-" + (labelC + NUMBER_OF_REALMS_IN_EACH_REPORT));
+    }
+
+    @Override
+    public void tearDown() {
+    }
+
+}
diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java
new file mode 100644
index 0000000..29bd33c
--- /dev/null
+++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java
@@ -0,0 +1,120 @@
+package org.keycloak.testsuite.performance;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.SocialLinkModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CreateUsersWorker implements Worker {
+
+    private static final Logger log = LoggingManager.getLoggerForClass();
+
+    private static final int NUMBER_OF_USERS_IN_EACH_REPORT = 5000;
+
+    // Total number of users created during whole test
+    private static AtomicInteger totalUserCounter = new AtomicInteger();
+
+    // Adding users will always start from 1. Each worker thread needs to add users to single realm, which is dedicated just for this worker
+    private int userCounterInRealm = 0;
+    private String realmId;
+
+    private int realmsOffset;
+    private boolean addBasicUserAttributes;
+    private boolean addDefaultRoles;
+    private boolean addPassword;
+    private int socialLinksPerUserCount;
+
+    @Override
+    public void setup(int workerId, KeycloakSession identitySession) {
+        realmsOffset = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.realms.offset", Integer.class);
+        addBasicUserAttributes = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.addBasicUserAttributes", Boolean.class);
+        addDefaultRoles = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.addDefaultRoles", Boolean.class);
+        addPassword = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.addPassword", Boolean.class);
+        socialLinksPerUserCount = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.socialLinksPerUserCount", Integer.class);
+
+        int realmNumber = realmsOffset + workerId;
+        realmId = PerfTestUtils.getRealmName(realmNumber);
+
+        StringBuilder logBuilder = new StringBuilder("Read setup: ")
+                .append("realmsOffset=" + realmsOffset)
+                .append(", addBasicUserAttributes=" + addBasicUserAttributes)
+                .append(", addDefaultRoles=" + addDefaultRoles)
+                .append(", addPassword=" + addPassword)
+                .append(", socialLinksPerUserCount=" + socialLinksPerUserCount)
+                .append(", realmId=" + realmId);
+        log.info(logBuilder.toString());
+    }
+
+    @Override
+    public void run(SampleResult result, KeycloakSession identitySession) {
+        // We need to obtain realm first
+        RealmModel realm = identitySession.getRealm(realmId);
+        if (realm == null) {
+            throw new IllegalStateException("Realm '" + realmId + "' not found");
+        }
+
+        int userNumber = ++userCounterInRealm;
+        int totalUserNumber = totalUserCounter.incrementAndGet();
+
+        String username = PerfTestUtils.getUsername(userNumber);
+
+        UserModel user = realm.addUser(username);
+
+        // Add basic user attributes (NOTE: Actually backend is automatically upgraded during each setter call)
+        if (addBasicUserAttributes) {
+            user.setFirstName(username + "FN");
+            user.setLastName(username + "LN");
+            user.setEmail(username + "@email.com");
+        }
+
+        // Adding default roles of realm to user
+        if (addDefaultRoles) {
+            for (RoleModel role : realm.getDefaultRoles()) {
+                realm.grantRole(user, role);
+            }
+        }
+
+        // Creating password (will be same as username)
+        if (addPassword) {
+            UserCredentialModel password = new UserCredentialModel();
+            password.setType(CredentialRepresentation.PASSWORD);
+            password.setValue(username);
+            realm.updateCredential(user, password);
+        }
+
+        // Creating some socialLinks
+        for (int i=0 ; i<socialLinksPerUserCount ; i++) {
+            String socialProvider;
+            switch (i) {
+                case 0: socialProvider = "facebook"; break;
+                case 1: socialProvider = "twitter"; break;
+                case 2: socialProvider = "google"; break;
+                default: throw new IllegalArgumentException("Total number of socialLinksPerUserCount is " + socialLinksPerUserCount
+                        + " which is too big.");
+            }
+
+            SocialLinkModel socialLink = new SocialLinkModel(socialProvider, username);
+            realm.addSocialLink(user, socialLink);
+        }
+
+        log.info("Finished creation of user " + username + " in realm: " + realm.getId());
+
+        int labelC = ((totalUserNumber - 1) / NUMBER_OF_USERS_IN_EACH_REPORT) * NUMBER_OF_USERS_IN_EACH_REPORT;
+        result.setSampleLabel("CreateUsers " + (labelC + 1) + "-" + (labelC + NUMBER_OF_USERS_IN_EACH_REPORT));
+    }
+
+    @Override
+    public void tearDown() {
+    }
+}
diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java
new file mode 100644
index 0000000..75b3554
--- /dev/null
+++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java
@@ -0,0 +1,46 @@
+package org.keycloak.testsuite.performance;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PerfTestUtils {
+
+    public static <T> T readSystemProperty(String propertyName, Class<T> expectedClass) {
+        String propAsString = System.getProperty(propertyName);
+        if (propAsString == null || propAsString.length() == 0) {
+            throw new IllegalArgumentException("Property '" + propertyName + "' not specified");
+        }
+
+        if (Integer.class.equals(expectedClass)) {
+            return expectedClass.cast(Integer.parseInt(propAsString));
+        } else if (Boolean.class.equals(expectedClass)) {
+            return expectedClass.cast(Boolean.valueOf(propAsString));
+        }  else {
+            throw new IllegalArgumentException("Not supported type " + expectedClass);
+        }
+    }
+
+    public static String getRealmName(int realmNumber) {
+        return "realm" + realmNumber;
+    }
+
+    public static String getApplicationName(int realmNumber, int applicationNumber) {
+        return getRealmName(realmNumber) + "application" + applicationNumber;
+    }
+
+    public static String getRoleName(int realmNumber, int roleNumber) {
+        return getRealmName(realmNumber) + "role" + roleNumber;
+    }
+
+    public static String getDefaultRoleName(int realmNumber, int defaultRoleNumber) {
+        return getRealmName(realmNumber) + "defrole" + defaultRoleNumber;
+    }
+
+    public static String getApplicationRoleName(int realmNumber, int applicationNumber, int roleNumber) {
+        return getApplicationName(realmNumber, applicationNumber) + "role" + roleNumber;
+    }
+
+    public static String getUsername(int userNumber) {
+        return "user" + userNumber;
+    }
+}
diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java
new file mode 100644
index 0000000..416cd60
--- /dev/null
+++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java
@@ -0,0 +1,127 @@
+package org.keycloak.testsuite.performance;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.SocialLinkModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ReadUsersWorker implements Worker {
+
+    private static final Logger log = LoggingManager.getLoggerForClass();
+
+    private static final int NUMBER_OF_ITERATIONS_IN_EACH_REPORT = 5000;
+
+    // Total number of iterations read during whole test
+    private static AtomicInteger totalIterationCounter = new AtomicInteger();
+
+    // Reading users will always start from 1. Each worker thread needs to read users to single realm, which is dedicated just for this worker
+    private int userCounterInRealm = 0;
+
+    private int realmsOffset;
+    private int readUsersPerIteration;
+    private int countOfUsersPerRealm;
+    private boolean readRoles;
+    private boolean readScopes;
+    private boolean readPassword;
+    private boolean readSocialLinks;
+    private boolean searchBySocialLinks;
+
+    private String realmId;
+    private int iterationNumber;
+
+    @Override
+    public void setup(int workerId, KeycloakSession identitySession) {
+        realmsOffset = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.realms.offset", Integer.class);
+        readUsersPerIteration = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readUsersPerIteration", Integer.class);
+        countOfUsersPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.countOfUsersPerRealm", Integer.class);
+        readRoles = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readRoles", Boolean.class);
+        readScopes = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readScopes", Boolean.class);
+        readPassword = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readPassword", Boolean.class);
+        readSocialLinks = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readSocialLinks", Boolean.class);
+        searchBySocialLinks = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.searchBySocialLinks", Boolean.class);
+
+        int realmNumber = realmsOffset + workerId;
+        realmId = PerfTestUtils.getRealmName(realmNumber);
+
+        StringBuilder logBuilder = new StringBuilder("Read setup: ")
+                .append("realmsOffset=" + realmsOffset)
+                .append(", readUsersPerIteration=" + readUsersPerIteration)
+                .append(", countOfUsersPerRealm=" + countOfUsersPerRealm)
+                .append(", readRoles=" + readRoles)
+                .append(", readScopes=" + readScopes)
+                .append(", readPassword=" + readPassword)
+                .append(", readSocialLinks=" + readSocialLinks)
+                .append(", searchBySocialLinks=" + searchBySocialLinks)
+                .append(", realmId=" + realmId);
+        log.info(logBuilder.toString());
+    }
+
+    @Override
+    public void run(SampleResult result, KeycloakSession identitySession) {
+        // We need to obtain realm first
+        RealmModel realm = identitySession.getRealm(realmId);
+        if (realm == null) {
+            throw new IllegalStateException("Realm '" + realmId + "' not found");
+        }
+
+        int totalIterationNumber = totalIterationCounter.incrementAndGet();
+        String lastUsername = null;
+
+        for (int i=0 ; i<readUsersPerIteration ; i++) {
+            ++userCounterInRealm;
+
+            // Start reading users from 1
+            if (userCounterInRealm > countOfUsersPerRealm) {
+                userCounterInRealm = 1;
+            }
+
+            String username = PerfTestUtils.getUsername(userCounterInRealm);
+            lastUsername = username;
+
+            UserModel user = realm.getUser(username);
+
+            // Read roles of user in realm
+            if (readRoles) {
+                realm.getRoleMappings(user);
+            }
+
+            // Read scopes of user in realm
+            if (readScopes) {
+                realm.getScopeMappings(user);
+            }
+
+            // Validate password (shoould be same as username)
+            if (readPassword) {
+                realm.validatePassword(user, username);
+            }
+
+            // Read socialLinks of user
+            if (readSocialLinks) {
+                realm.getSocialLinks(user);
+            }
+
+            // Try to search by social links
+            if (searchBySocialLinks) {
+                SocialLinkModel socialLink = new SocialLinkModel("facebook", username);
+                realm.getUserBySocialLink(socialLink);
+            }
+        }
+
+        log.info("Finished iteration " + ++iterationNumber + " in ReadUsers test for " + realmId + " worker. Last read user " + lastUsername  + " in realm: " + realmId);
+
+        int labelC = ((totalIterationNumber - 1) / NUMBER_OF_ITERATIONS_IN_EACH_REPORT) * NUMBER_OF_ITERATIONS_IN_EACH_REPORT;
+        result.setSampleLabel("ReadUsers " + (labelC + 1) + "-" + (labelC + NUMBER_OF_ITERATIONS_IN_EACH_REPORT));
+    }
+
+    @Override
+    public void tearDown() {
+    }
+}
diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java
new file mode 100644
index 0000000..262ad93
--- /dev/null
+++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java
@@ -0,0 +1,72 @@
+package org.keycloak.testsuite.performance;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.utils.PropertiesManager;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RemoveUsersWorker implements Worker {
+
+    private static final Logger log = LoggingManager.getLoggerForClass();
+
+    private static final int NUMBER_OF_USERS_IN_EACH_REPORT = 5000;
+
+    // Total number of users removed during whole test
+    private static AtomicInteger totalUserCounter = new AtomicInteger();
+
+    // Removing users will always start from 1. Each worker thread needs to add users to single realm, which is dedicated just for this worker
+    private int userCounterInRealm = 0;
+    private RealmModel realm;
+
+    private int realmsOffset;
+
+    @Override
+    public void setup(int workerId, KeycloakSession identitySession) {
+        realmsOffset = PerfTestUtils.readSystemProperty("keycloak.perf.removeUsers.realms.offset", Integer.class);
+
+        int realmNumber = realmsOffset + workerId;
+        String realmId = PerfTestUtils.getRealmName(realmNumber);
+        realm = identitySession.getRealm(realmId);
+        if (realm == null) {
+            throw new IllegalStateException("Realm '" + realmId + "' not found");
+        }
+
+        log.info("Read setup: realmsOffset=" + realmsOffset);
+    }
+
+    @Override
+    public void run(SampleResult result, KeycloakSession identitySession) {
+        throw new IllegalStateException("Not yet supported");
+        /*
+        int userNumber = ++userCounterInRealm;
+        int totalUserNumber = totalUserCounter.incrementAndGet();
+
+        String username = PerfTestUtils.getUsername(userNumber);
+
+        // TODO: Not supported in model actually. We support operation just in MongoDB
+        // UserModel user = realm.removeUser(username);
+        if (PropertiesManager.isMongoSessionFactory()) {
+            RealmAdapter mongoRealm = (RealmAdapter)realm;
+            mongoRealm.removeUser(username);
+        } else {
+            throw new IllegalArgumentException("Actually removing of users is supported just for MongoDB");
+        }
+
+        log.info("Finished removing of user " + username + " in realm: " + realm.getId());
+
+        int labelC = ((totalUserNumber - 1) / NUMBER_OF_USERS_IN_EACH_REPORT) * NUMBER_OF_USERS_IN_EACH_REPORT;
+        result.setSampleLabel("ReadUsers " + (labelC + 1) + "-" + (labelC + NUMBER_OF_USERS_IN_EACH_REPORT));
+        */
+    }
+
+    @Override
+    public void tearDown() {
+    }
+}
diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/Worker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/Worker.java
new file mode 100644
index 0000000..69732cf
--- /dev/null
+++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/Worker.java
@@ -0,0 +1,17 @@
+package org.keycloak.testsuite.performance;
+
+import org.apache.jmeter.samplers.SampleResult;
+import org.keycloak.models.KeycloakSession;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface Worker {
+
+    void setup(int workerId, KeycloakSession identitySession);
+
+    void run(SampleResult result, KeycloakSession identitySession);
+
+    void tearDown();
+
+}
diff --git a/testsuite/performance/src/test/jmeter/jmeter.properties b/testsuite/performance/src/test/jmeter/jmeter.properties
new file mode 100644
index 0000000..39b6ce4
--- /dev/null
+++ b/testsuite/performance/src/test/jmeter/jmeter.properties
@@ -0,0 +1,20 @@
+#Thu Mar 07 18:46:04 BRT 2013
+not_in_menu=HTML Parameter Mask,HTTP User Parameter Modifier
+xml.parser=org.apache.xerces.parsers.SAXParser
+cookies=cookies
+wmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser
+HTTPResponse.parsers=htmlParser wmlParser
+remote_hosts=127.0.0.1
+system.properties=system.properties
+beanshell.server.file=../extras/startup.bsh
+log_level.jmeter.junit=DEBUG
+sampleresult.timestamp.start=true
+jmeter.laf.mac=System
+log_level.jorphan=INFO
+classfinder.functions.contain=.functions.
+user.properties=user.properties
+wmlParser.types=text/vnd.wap.wml
+log_level.jmeter=DEBUG
+classfinder.functions.notContain=.gui.
+htmlParser.types=text/html application/xhtml+xml application/xml text/xml
+upgrade_properties=/bin/upgrade.properties
\ No newline at end of file
diff --git a/testsuite/performance/src/test/jmeter/keycloak_perf_test.jmx b/testsuite/performance/src/test/jmeter/keycloak_perf_test.jmx
new file mode 100644
index 0000000..b96dcf3
--- /dev/null
+++ b/testsuite/performance/src/test/jmeter/keycloak_perf_test.jmx
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="2.4" jmeter="2.9 r1437961">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments"></stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">10</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">0</stringProp>
+        <longProp name="ThreadGroup.start_time">1362689985000</longProp>
+        <longProp name="ThreadGroup.end_time">1362689985000</longProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <JavaSampler guiclass="JavaTestSamplerGui" testclass="JavaSampler" testname="Java Request" enabled="true">
+          <elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true">
+            <collectionProp name="Arguments.arguments">
+            </collectionProp>
+          </elementProp>
+          <stringProp name="classname">org.keycloak.testsuite.performance.BaseJMeterPerformanceTest</stringProp>
+        </JavaSampler>
+      </hashTree>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>
\ No newline at end of file
diff --git a/testsuite/performance/src/test/jmeter/system.properties b/testsuite/performance/src/test/jmeter/system.properties
new file mode 100644
index 0000000..26d24aa
--- /dev/null
+++ b/testsuite/performance/src/test/jmeter/system.properties
@@ -0,0 +1,79 @@
+## Choose implementation of KeycloakSessionFactory
+# keycloak.sessionFactory=picketlink
+keycloak.sessionFactory=mongo
+
+## Configure JPA (just hbm2ddl schema configurable here. Rest of the stuff in META-INF/persistence.xml)
+keycloak.jpa.hbm2ddl.auto=create
+# keycloak.jpa.hbm2ddl.auto=update
+
+
+## Configure MongoDB (Useful just when keycloak.sessionFactory=mongo)
+keycloak.mongodb.host=localhost
+keycloak.mongodb.port=27017
+keycloak.mongodb.databaseName=keycloakPerfTest
+# Should be DB dropped at startup of the test?
+keycloak.mongodb.dropDatabaseOnStartup=true
+
+
+## Specify Keycloak worker class
+keycloak.perf.workerClass=org.keycloak.testsuite.performance.CreateRealmsWorker
+# keycloak.perf.workerClass=org.keycloak.testsuite.performance.CreateUsersWorker
+# keycloak.perf.workerClass=org.keycloak.testsuite.performance.ReadUsersWorker
+# keycloak.perf.workerClass=org.keycloak.testsuite.performance.RemoveUsersWorker
+
+
+## Properties for CreateRealms test. This test is used to create some realms.
+# Each iteration of single worker thread will add one realm and it will add some roles, defaultRoles, credentials and applications to it
+# Offset where to start creating realms. Count (total number of realms to create) is configurable as number of JMeter threads*loopCount
+# For example: if offset==1 and in JMeter properties we have LoopController.loops=10 and num_threads=2 then we will create 20 realms in total and we will create realms "realm1" - "realm10"
+# NOTE: Count (total number of realms to create) is configurable as number of JMeter threads*loopCount
+keycloak.perf.createRealms.realms.offset=1
+# Count of apps per each realm (For example if count=5, we will create apps like "realm1app1" - "realm1app5" for realm "realm1"
+# and similarly for all other created realms)
+keycloak.perf.createRealms.appsPerRealm=5
+# Count of roles per each realm (For example if count=5, we will create roles like "realm1role1" - "realm1role5" for realm "realm1"
+# and similarly for all other created realms)
+keycloak.perf.createRealms.rolesPerRealm=5
+# Count of default roles per each realm (For example if count=2, we will create roles like "realm1defrole1" and "realm1defrole2"
+# for realm "realm1" and similarly for all other created realms)
+keycloak.perf.createRealms.defaultRolesPerRealm=2
+# Count of roles per each application (For example if count=3 we will have roles "realm1app1role1" - "realm1app1role3" for realm=1 and application=1
+# (if realmsCount=10, appsPerRealm=5 it will be 150 application roles totally)
+keycloak.perf.createRealms.rolesPerApp=3
+# Whether to create required credentials in each realm (If true, we will create "password", "totp" and client-certificate)
+keycloak.perf.createRealms.createRequiredCredentials=true
+
+
+## Properties for CreateUsers test. This test is used to create some users
+# Each iteration of single worker thread will add one user and it will add some default roles, passwords and bind him with some social accounts
+# Each worker will use separate realm dedicated just for him, so each worker will create user1, user2, ... , userN . N (number of users to create per realm)
+# is configurable in JMeter configuration as loopCount. Total number of created users for whole test will be threads*loopCount
+# NOTE: For each thread, the corresponding realm must already exists
+# Realm where to start creating users
+keycloak.perf.createUsers.realms.offset=1
+# Whether to add basic attributes like firstName/lastName/email to each user
+keycloak.perf.createUsers.addBasicUserAttributes=true
+# Whether to add all default roles of realm to this user
+keycloak.perf.createUsers.addDefaultRoles=true
+# Whether to add password to this user
+keycloak.perf.createUsers.addPassword=true
+# Number of social links to create for each user. Possible values are 0, 1, 2, 3 (For 3 it will create Facebook, Twitter and Google)
+keycloak.perf.createUsers.socialLinksPerUserCount=0
+
+
+## Properties for ReadUsers test. This test is used to read some users from DB and alternatively read some of his properties (passwords, roles, scopes, socialLinks)
+keycloak.perf.readUsers.realms.offset=1
+# Number of read users in each iteration
+keycloak.perf.readUsers.readUsersPerIteration=5
+# Number of users to read in each realm. After reading all 2000 users, reading will start again from user1
+keycloak.perf.readUsers.countOfUsersPerRealm=2000
+keycloak.perf.readUsers.readRoles=true
+keycloak.perf.readUsers.readScopes=true
+keycloak.perf.readUsers.readPassword=true
+keycloak.perf.readUsers.readSocialLinks=false
+keycloak.perf.readUsers.searchBySocialLinks=false
+
+
+## Properties for RemoveUsers worker. This test is used to remove some users from DB (and all their stuff actually)
+# Similarly like in CreateUsers test, each worker works just with one realm. Number of removed users depends on JMeter property loopCount
+keycloak.perf.removeUsers.realms.offset=1
diff --git a/testsuite/performance/src/test/resources/META-INF/persistence.xml b/testsuite/performance/src/test/resources/META-INF/persistence.xml
new file mode 100644
index 0000000..1dff641
--- /dev/null
+++ b/testsuite/performance/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,40 @@
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
+             version="1.0">
+    <persistence-unit name="keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
+        <provider>org.hibernate.ejb.HibernatePersistence</provider>
+
+        <class>org.picketlink.idm.jpa.model.sample.simple.AttributedTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.AccountTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.RoleTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.GroupTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.IdentityTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.RelationshipTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.RelationshipIdentityTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.PartitionTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.PasswordCredentialTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.DigestCredentialTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.X509CredentialTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.OTPCredentialTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.AttributeTypeEntity</class>
+        <class>org.keycloak.services.models.picketlink.mappings.RealmEntity</class>
+        <class>org.keycloak.services.models.picketlink.mappings.ApplicationEntity</class>
+
+        <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+        <properties>
+            <property name="hibernate.connection.url" value="jdbc:mysql://localhost/keycloakPerfTest"/>
+            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
+            <property name="hibernate.connection.username" value="portal"/>
+            <property name="hibernate.connection.password" value="portal"/>
+            <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
+            <!--<property name="hibernate.hbm2ddl.auto" value="update" />-->
+            <property name="hibernate.hbm2ddl.auto" value="${keycloak.jpa.hbm2ddl.auto}" />
+            <property name="hibernate.show_sql" value="false" />
+            <property name="hibernate.format_sql" value="true" />
+        </properties>
+    </persistence-unit>
+
+
+</persistence>
\ No newline at end of file

testsuite/pom.xml 217(+7 -210)

diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 7c5278d..c8b7bcf 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -8,216 +8,13 @@
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
 
-	<artifactId>keycloak-testsuite</artifactId>
-	<name>Keycloak TestSuite</name>
+	<artifactId>keycloak-testsuite-pom</artifactId>
+    <packaging>pom</packaging>
+    <name>Keycloak TestSuite</name>
 	<description />
+    <modules>
+        <module>integration</module>
+        <module>performance</module>
+    </modules>
 
-	<dependencyManagement>
-		<dependencies>
-			<dependency>
-				<groupId>org.keycloak</groupId>
-				<artifactId>keycloak-as7-adapter</artifactId>
-				<version>${project.version}</version>
-			</dependency>
-		</dependencies>
-	</dependencyManagement>
-
-	<dependencies>
-        <dependency>
-            <groupId>org.bouncycastle</groupId>
-            <artifactId>bcprov-jdk16</artifactId>
-        </dependency>
-		<dependency>
-			<groupId>org.keycloak</groupId>
-			<artifactId>keycloak-core</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-		<dependency>
-			<groupId>org.keycloak</groupId>
-			<artifactId>keycloak-services</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-        <!--
-		<dependency>
-			<groupId>org.keycloak</groupId>
-			<artifactId>keycloak-server</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-		-->
-		<dependency>
-			<groupId>org.keycloak</groupId>
-			<artifactId>keycloak-social-core</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-		<dependency>
-			<groupId>org.keycloak</groupId>
-			<artifactId>keycloak-social-google</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-		<dependency>
-			<groupId>org.keycloak</groupId>
-			<artifactId>keycloak-social-twitter</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-		<dependency>
-			<groupId>org.keycloak</groupId>
-			<artifactId>keycloak-social-facebook</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-		<dependency>
-			<groupId>org.keycloak</groupId>
-			<artifactId>keycloak-forms</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-		
-        <dependency>
-            <groupId>org.jboss.logging</groupId>
-            <artifactId>jboss-logging</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-idm-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-common</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-idm-impl</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-idm-simple-schema</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-config</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.resteasy</groupId>
-            <artifactId>resteasy-jaxrs</artifactId>
-            <exclusions>
-                <exclusion>
-                    <groupId>log4j</groupId>
-                    <artifactId>log4j</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.slf4j</groupId>
-                    <artifactId>slf4j-api</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.slf4j</groupId>
-                    <artifactId>slf4j-simple</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-         <dependency>
-            <groupId>org.jboss.resteasy</groupId>
-            <artifactId>jaxrs-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.resteasy</groupId>
-            <artifactId>resteasy-client</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.resteasy</groupId>
-            <artifactId>resteasy-crypto</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.resteasy</groupId>
-            <artifactId>jose-jwt</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.resteasy</groupId>
-            <artifactId>resteasy-undertow</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>io.undertow</groupId>
-            <artifactId>undertow-servlet</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>io.undertow</groupId>
-            <artifactId>undertow-core</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.codehaus.jackson</groupId>
-            <artifactId>jackson-core-asl</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.codehaus.jackson</groupId>
-            <artifactId>jackson-mapper-asl</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.spec.javax.servlet</groupId>
-            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.codehaus.jackson</groupId>
-            <artifactId>jackson-xc</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.hibernate.javax.persistence</groupId>
-            <artifactId>hibernate-jpa-2.0-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.h2database</groupId>
-            <artifactId>h2</artifactId>
-            <version>1.3.161</version>
-        </dependency>
-        <dependency>
-            <groupId>org.hibernate</groupId>
-            <artifactId>hibernate-entitymanager</artifactId>
-            <version>3.6.6.Final</version>
-        </dependency>
-		<dependency>
-    		<groupId>com.icegreen</groupId>
-    		<artifactId>greenmail</artifactId>
-		</dependency>     
-		<dependency>
-			<groupId>org.seleniumhq.selenium</groupId>
-			<artifactId>selenium-java</artifactId>
-  		</dependency>
-	</dependencies>
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-compiler-plugin</artifactId>
-				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
-				</configuration>
-			</plugin>
-		</plugins>
-	</build>
-
-	<profiles>
-		<profile>
-			<id>jboss-managed</id>
-			<dependencies>
-				<dependency>
-					<groupId>org.jboss.as</groupId>
-					<artifactId>jboss-as-arquillian-container-managed</artifactId>
-					<scope>test</scope>
-					<version>7.1.1.Final</version>
-				</dependency>
-			</dependencies>
-		</profile>
-		<profile>
-			<id>jboss-remote</id>
-			<dependencies>
-				<dependency>
-					<groupId>org.jboss.as</groupId>
-					<artifactId>jboss-as-arquillian-container-remote</artifactId>
-					<scope>test</scope>
-					<version>7.1.1.Final</version>
-				</dependency>
-			</dependencies>
-		</profile>
-	</profiles>
 </project>