keycloak-memoizeit
Changes
services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java 11(+10 -1)
services/src/main/java/org/keycloak/services/models/nosql/impl/types/EnumToStringConverter.java 26(+26 -0)
services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java 2(+1 -1)
services/src/main/java/org/keycloak/services/models/nosql/impl/types/StringToEnumConverter.java 32(+32 -0)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java 23(+22 -1)
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
+ }
}