keycloak-memoizeit

Adapt MongoDB impl with latest changes on UserModel and RealmModel.

9/18/2013 6:51:42 PM

Details

diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java
index fd2020a..00c8e5e 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java
@@ -60,17 +60,17 @@ public class TypeConverter {
                 converter = (Converter<Object, S>)appObjects.values().iterator().next();
             } else {
                 // Try to find converter for requested application type
-                converter = (Converter<Object, S>)appObjects.get(expectedApplicationObjectType);
+                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())) {
+        /*if (!expectedApplicationObjectType.isAssignableFrom(converter.getExpectedReturnType())) {
             throw new IllegalArgumentException("Converter " + converter + " has return type " + converter.getExpectedReturnType() +
                     " but we need type " + expectedApplicationObjectType);
-        }
+        } */
 
         return converter.convertObject(dbObject);
     }
@@ -78,7 +78,7 @@ public class TypeConverter {
 
     public <S> S convertApplicationObjectToDBObject(Object applicationObject, Class<S> expectedDBObjectType) {
         Class<?> appObjectType = applicationObject.getClass();
-        Converter<Object, S> converter = (Converter<Object, S>)getAppConverterForType(appObjectType);
+        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");
         }
@@ -90,14 +90,14 @@ public class TypeConverter {
     }
 
     // Try to find converter for given type or all it's supertypes
-    private Converter<Object, ?> getAppConverterForType(Class<?> appObjectType) {
+    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);
+                converter = getAppConverterForType(interface1, appObjectConverters);
                 if (converter != null) {
                     return converter;
                 }
@@ -105,7 +105,7 @@ public class TypeConverter {
 
             Class<?> superType = appObjectType.getSuperclass();
             if (superType != null) {
-                return getAppConverterForType(superType);
+                return getAppConverterForType(superType, appObjectConverters);
             } else {
                 return null;
             }
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java
index 44a340a..a6b4930 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java
@@ -24,11 +24,13 @@ import org.keycloak.services.models.nosql.api.query.NoSQLQuery;
 import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder;
 import org.keycloak.services.models.nosql.api.types.Converter;
 import org.keycloak.services.models.nosql.api.types.TypeConverter;
+import org.keycloak.services.models.nosql.impl.types.EnumToStringConverter;
 import org.keycloak.services.models.nosql.impl.types.ListConverter;
 import org.keycloak.services.models.nosql.impl.types.BasicDBListConverter;
 import org.keycloak.services.models.nosql.impl.types.BasicDBObjectConverter;
 import org.keycloak.services.models.nosql.impl.types.NoSQLObjectConverter;
 import org.keycloak.services.models.nosql.impl.types.SimpleConverter;
+import org.keycloak.services.models.nosql.impl.types.StringToEnumConverter;
 import org.picketlink.common.properties.Property;
 import org.picketlink.common.properties.query.AnnotatedPropertyCriteria;
 import org.picketlink.common.properties.query.PropertyQueries;
@@ -64,6 +66,10 @@ public class MongoDBImpl implements NoSQL {
         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));
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java
index 87941d3..75dae75 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java
@@ -58,7 +58,16 @@ public class BasicDBListConverter implements Converter<BasicDBList, ArrayList> {
                 throw new RuntimeException(cnfe);
             }
         } else {
-            return Object.class;
+            // 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/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ClassCache.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ClassCache.java
new file mode 100644
index 0000000..cf4372b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ClassCache.java
@@ -0,0 +1,37 @@
+package org.keycloak.services.models.nosql.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/services/src/main/java/org/keycloak/services/models/nosql/impl/types/EnumToStringConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/EnumToStringConverter.java
new file mode 100644
index 0000000..fd32db5
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/EnumToStringConverter.java
@@ -0,0 +1,26 @@
+package org.keycloak.services.models.nosql.impl.types;
+
+import org.keycloak.services.models.nosql.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/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java
index c244607..cd78367 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java
@@ -40,7 +40,7 @@ public class NoSQLObjectConverter<T extends NoSQLObject> implements Converter<T,
             String propName = property.getName();
             Object propValue = property.getValue(applicationObject);
 
-            Object dbValue = propValue == null ? null : typeConverter.convertApplicationObjectToDBObject(propValue, Types.boxedClass(property.getJavaClass()));
+            Object dbValue = propValue == null ? null : typeConverter.convertApplicationObjectToDBObject(propValue, Object.class);
             dbObject.put(propName, dbValue);
         }
 
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/StringToEnumConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/StringToEnumConverter.java
new file mode 100644
index 0000000..d405943
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/StringToEnumConverter.java
@@ -0,0 +1,32 @@
+package org.keycloak.services.models.nosql.impl.types;
+
+import org.keycloak.services.models.nosql.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/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java
index 85c9fcd..24b0146 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java
@@ -140,6 +140,17 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public boolean isVerifyEmail() {
+        return realm.isVerifyEmail();
+    }
+
+    @Override
+    public void setVerifyEmail(boolean verifyEmail) {
+        realm.setVerifyEmail(verifyEmail);
+        updateRealm();
+    }
+
+    @Override
     public int getTokenLifespan() {
         return realm.getTokenLifespan();
     }
@@ -162,6 +173,17 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public int getAccessCodeLifespanUserAction() {
+        return realm.getAccessCodeLifespanUserAction();
+    }
+
+    @Override
+    public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
+        realm.setAccessCodeLifespanUserAction(accessCodeLifespanUserAction);
+        updateRealm();
+    }
+
+    @Override
     public String getPublicKeyPem() {
         return realm.getPublicKeyPem();
     }
@@ -266,7 +288,6 @@ public class RealmAdapter implements RealmModel {
 
         UserData userData = new UserData();
         userData.setLoginName(username);
-        userData.setEnabled(true);
         userData.setRealmId(getOid());
 
         noSQL.saveObject(userData);
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java
index e908568..24c3a29 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java
@@ -1,5 +1,6 @@
 package org.keycloak.services.models.nosql.keycloak.adapters;
 
+import java.util.List;
 import java.util.Map;
 
 import org.keycloak.services.models.UserModel;
@@ -32,9 +33,15 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public void setEnabled(boolean enabled) {
-       user.setEnabled(enabled);
-       noSQL.saveObject(user);
+    public void setStatus(Status status) {
+        user.setStatus(status);
+        noSQL.saveObject(user);
+    }
+
+    @Override
+    public Status getStatus() {
+        Status status = user.getStatus();
+        return status != null ? status : Status.ENABLED;
     }
 
     @Override
@@ -71,6 +78,17 @@ public class UserAdapter implements UserModel {
     }
 
     @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);
     }
@@ -94,4 +112,37 @@ public class UserAdapter implements UserModel {
     public UserData getUser() {
         return user;
     }
+
+    @Override
+    public List<RequiredAction> getRequiredActions() {
+        List<RequiredAction> requiredActions = user.getRequiredActions();
+
+        // Compatibility with picketlink impl
+        if (requiredActions == null || requiredActions.size() == 0) {
+            return null;
+        }
+
+        return requiredActions;
+    }
+
+    @Override
+    public void addRequiredAction(RequiredAction 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/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java
index 8152c5b..70d0a51 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java
@@ -26,10 +26,12 @@ public class RealmData implements NoSQLObject {
     private boolean sslNotRequired;
     private boolean cookieLoginAllowed;
     private boolean registrationAllowed;
+    private boolean verifyEmail;
     private boolean social;
     private boolean automaticRegistrationAfterSocialLogin;
     private int tokenLifespan;
     private int accessCodeLifespan;
+    private int accessCodeLifespanUserAction;
     private String publicKeyPem;
     private String privateKeyPem;
 
@@ -100,6 +102,15 @@ public class RealmData implements NoSQLObject {
     }
 
     @NoSQLField
+    public boolean isVerifyEmail() {
+        return verifyEmail;
+    }
+
+    public void setVerifyEmail(boolean verifyEmail) {
+        this.verifyEmail = verifyEmail;
+    }
+
+    @NoSQLField
     public boolean isSocial() {
         return social;
     }
@@ -136,6 +147,15 @@ public class RealmData implements NoSQLObject {
     }
 
     @NoSQLField
+    public int getAccessCodeLifespanUserAction() {
+        return accessCodeLifespanUserAction;
+    }
+
+    public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
+        this.accessCodeLifespanUserAction = accessCodeLifespanUserAction;
+    }
+
+    @NoSQLField
     public String getPublicKeyPem() {
         return publicKeyPem;
     }
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java
index 83c7151..6206289 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java
@@ -3,6 +3,7 @@ package org.keycloak.services.models.nosql.keycloak.data;
 import java.util.List;
 
 import org.jboss.resteasy.logging.Logger;
+import org.keycloak.services.models.UserModel;
 import org.keycloak.services.models.nosql.api.AbstractAttributedNoSQLObject;
 import org.keycloak.services.models.nosql.api.NoSQL;
 import org.keycloak.services.models.nosql.api.NoSQLCollection;
@@ -24,12 +25,15 @@ public class UserData extends AbstractAttributedNoSQLObject {
     private String firstName;
     private String lastName;
     private String email;
-    private boolean enabled;
+    private boolean emailVerified;
+    private boolean totp;
+    private UserModel.Status status;
 
     private String realmId;
 
     private List<String> roleIds;
     private List<String> scopeIds;
+    private List<UserModel.RequiredAction> requiredActions;
 
     @NoSQLId
     public String getId() {
@@ -77,12 +81,34 @@ public class UserData extends AbstractAttributedNoSQLObject {
     }
 
     @NoSQLField
+    public boolean isEmailVerified() {
+        return emailVerified;
+    }
+
+    public void setEmailVerified(boolean emailVerified) {
+        this.emailVerified = emailVerified;
+    }
+
     public boolean isEnabled() {
-        return enabled;
+        return !UserModel.Status.DISABLED.equals(getStatus());
     }
 
-    public void setEnabled(boolean enabled) {
-        this.enabled = enabled;
+    @NoSQLField
+    public boolean isTotp() {
+        return totp;
+    }
+
+    public void setTotp(boolean totp) {
+        this.totp = totp;
+    }
+
+    @NoSQLField
+    public UserModel.Status getStatus() {
+        return status;
+    }
+
+    public void setStatus(UserModel.Status status) {
+        this.status = status;
     }
 
     @NoSQLField
@@ -112,6 +138,15 @@ public class UserData extends AbstractAttributedNoSQLObject {
         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()
diff --git a/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java b/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java
index 276ea3c..b59fdff 100644
--- a/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java
+++ b/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java
@@ -54,6 +54,7 @@ public class MongoDBModelTest {
         Person john = new Person();
         john.setFirstName("john");
         john.setAge(25);
+        john.setGender(Person.Gender.MALE);
 
         mongoDB.saveObject(john);
 
@@ -72,6 +73,9 @@ public class MongoDBModelTest {
         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());
@@ -99,5 +103,10 @@ public class MongoDBModelTest {
         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/services/src/test/java/org/keycloak/test/nosql/Person.java b/services/src/test/java/org/keycloak/test/nosql/Person.java
index fa091fa..3ead512 100644
--- a/services/src/test/java/org/keycloak/test/nosql/Person.java
+++ b/services/src/test/java/org/keycloak/test/nosql/Person.java
@@ -18,6 +18,10 @@ public class Person extends AbstractNoSQLObject {
     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() {
@@ -47,6 +51,24 @@ public class Person extends AbstractNoSQLObject {
     }
 
     @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;
     }
@@ -63,4 +85,17 @@ public class Person extends AbstractNoSQLObject {
     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
+    }
 }