Adapt MongoDB impl with latest changes on UserModel and RealmModel. Support for Enums in Converter SPI
This commit is contained in:
parent
58d862819a
commit
86cf090909
13 changed files with 299 additions and 18 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue