Adapt MongoDB impl with latest changes on UserModel and RealmModel. Support for Enums in Converter SPI

This commit is contained in:
mposolda 2013-09-18 23:51:42 +02:00
parent 58d862819a
commit 86cf090909
13 changed files with 299 additions and 18 deletions

View file

@ -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;
}

View file

@ -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));

View file

@ -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();
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -139,6 +139,17 @@ public class RealmAdapter implements RealmModel {
updateRealm();
}
@Override
public boolean isVerifyEmail() {
return realm.isVerifyEmail();
}
@Override
public void setVerifyEmail(boolean verifyEmail) {
realm.setVerifyEmail(verifyEmail);
updateRealm();
}
@Override
public int getTokenLifespan() {
return realm.getTokenLifespan();
@ -161,6 +172,17 @@ public class RealmAdapter implements RealmModel {
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();
@ -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);

View file

@ -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,11 +33,17 @@ public class UserAdapter implements UserModel {
}
@Override
public void setEnabled(boolean enabled) {
user.setEnabled(enabled);
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
public String getFirstName() {
return user.getFirstName();
@ -70,6 +77,17 @@ public class UserAdapter implements UserModel {
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);
@ -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);
}
}

View file

@ -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;
@ -99,6 +101,15 @@ public class RealmData implements NoSQLObject {
this.registrationAllowed = registrationAllowed;
}
@NoSQLField
public boolean isVerifyEmail() {
return verifyEmail;
}
public void setVerifyEmail(boolean verifyEmail) {
this.verifyEmail = verifyEmail;
}
@NoSQLField
public boolean isSocial() {
return social;
@ -135,6 +146,15 @@ public class RealmData implements NoSQLObject {
this.accessCodeLifespan = accessCodeLifespan;
}
@NoSQLField
public int getAccessCodeLifespanUserAction() {
return accessCodeLifespanUserAction;
}
public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
this.accessCodeLifespanUserAction = accessCodeLifespanUserAction;
}
@NoSQLField
public String getPublicKeyPem() {
return publicKeyPem;

View file

@ -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 isEnabled() {
return enabled;
public boolean isEmailVerified() {
return emailVerified;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
public void setEmailVerified(boolean emailVerified) {
this.emailVerified = emailVerified;
}
public boolean isEnabled() {
return !UserModel.Status.DISABLED.equals(getStatus());
}
@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()

View file

@ -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));
}
}

View file

@ -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() {
@ -46,6 +50,24 @@ public class Person extends AbstractNoSQLObject {
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;
@ -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
}
}