Mongo: Remove realmAdmins when realm is removed. Refactored Converters to support list of embedded objects

This commit is contained in:
mposolda 2013-09-10 16:52:13 +02:00
parent be48672ba6
commit 4db738689f
23 changed files with 741 additions and 350 deletions

View file

@ -4,6 +4,7 @@ import java.util.List;
import org.keycloak.services.models.nosql.api.query.NoSQLQuery;
import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder;
import org.picketlink.common.properties.Property;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -29,4 +30,8 @@ public interface NoSQL {
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);
}

View file

@ -1,6 +1,7 @@
package org.keycloak.services.models.nosql.api.query;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -21,7 +22,7 @@ public abstract class NoSQLQueryBuilder {
return this;
}
public abstract NoSQLQueryBuilder inCondition(String name, Object[] values);
public abstract NoSQLQueryBuilder inCondition(String name, List<?> values);
protected void put(String name, Object value) {
queryAttributes.put(name, value);

View file

@ -8,11 +8,9 @@ package org.keycloak.services.models.nosql.api.types;
*/
public interface Converter<T, S> {
T convertDBObjectToApplicationObject(S dbObject);
S convertObject(T objectToConvert);
S convertApplicationObjectToDBObject(T applicationObject);
Class<? extends T> getConverterObjectType();
Class<? extends T> getApplicationObjectType();
Class<S> getDBObjectType();
Class<S> getExpectedReturnType();
}

View file

@ -1,31 +0,0 @@
package org.keycloak.services.models.nosql.api.types;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
class ConverterKey {
private final Class<?> applicationObjectType;
private final Class<?> dbObjectType;
public ConverterKey(Class<?> applicationObjectType, Class<?> dbObjectType) {
this.applicationObjectType = applicationObjectType;
this.dbObjectType = dbObjectType;
}
@Override
public int hashCode() {
return applicationObjectType.hashCode() * 13 + dbObjectType.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || !obj.getClass().equals(this.getClass())) {
return false;
}
ConverterKey tc = (ConverterKey)obj;
return tc.applicationObjectType.equals(this.applicationObjectType) && tc.dbObjectType.equals(this.dbObjectType);
}
}

View file

@ -3,6 +3,8 @@ package org.keycloak.services.models.nosql.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.
@ -12,38 +14,101 @@ import java.util.Map;
public class TypeConverter {
// TODO: Thread-safety support (maybe...)
private Map<ConverterKey, Converter<?, ?>> converterRegistry = new HashMap<ConverterKey, Converter<?, ?>>();
// Converters of Application objects to DB objects
private Map<Class<?>, Converter<?, ?>> appObjectConverters = new HashMap<Class<?>, Converter<?, ?>>();
public <T, S> void addConverter(Converter<T, S> converter) {
ConverterKey converterKey = new ConverterKey(converter.getApplicationObjectType(), converter.getDBObjectType());
converterRegistry.put(converterKey, 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);
}
public <T, S> T convertDBObjectToApplicationObject(S dbObject, Class<T> expectedApplicationObjectType) {
// TODO: Not type safe as it expects that S type of converter must exactly match type of dbObject. Converter lookup should be more flexible
Class<S> expectedDBObjectType = (Class<S>)dbObject.getClass();
Converter<T, S> converter = getConverter(expectedApplicationObjectType, expectedDBObjectType);
return converter.convertDBObjectToApplicationObject(dbObject);
/**
* 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 <T, S> S convertApplicationObjectToDBObject(T applicationObject, Class<S> expectedDBObjectType) {
// TODO: Not type safe as it expects that T type of converter must exactly match type of applicationObject. Converter lookup should be more flexible
Class<T> expectedApplicationObjectType = (Class<T>)applicationObject.getClass();
Converter<T, S> converter = getConverter(expectedApplicationObjectType, expectedDBObjectType);
return converter.convertApplicationObjectToDBObject(applicationObject);
}
public <S> S convertDBObjectToApplicationObject(Object dbObject, Class<S> expectedApplicationObjectType) {
Class<?> dbObjectType = dbObject.getClass();
Converter<Object, S> converter;
private <T, S> Converter<T, S> getConverter( Class<T> expectedApplicationObjectType, Class<S> expectedDBObjectType) {
ConverterKey key = new ConverterKey(expectedApplicationObjectType, expectedDBObjectType);
Converter<T, S> converter = (Converter<T, S>)converterRegistry.get(key);
if (converter == null) {
throw new IllegalStateException("Can't found converter for expectedApplicationObject=" + expectedApplicationObjectType + ", expectedDBObjectType=" + expectedDBObjectType);
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>)appObjects.get(expectedApplicationObjectType);
}
}
return converter;
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);
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 Converter<Object, ?> getAppConverterForType(Class<?> appObjectType) {
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);
if (converter != null) {
return converter;
}
}
Class<?> superType = appObjectType.getSuperclass();
if (superType != null) {
return getAppConverterForType(superType);
} else {
return null;
}
}
}
}

View file

@ -1,11 +1,13 @@
package org.keycloak.services.models.nosql.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;
@ -22,8 +24,11 @@ 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.BasicDBListToStringArrayConverter;
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.picketlink.common.properties.Property;
import org.picketlink.common.properties.query.AnnotatedPropertyCriteria;
import org.picketlink.common.properties.query.PropertyQueries;
@ -33,6 +38,8 @@ import org.picketlink.common.properties.query.PropertyQueries;
*/
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);
@ -40,14 +47,27 @@ public class MongoDBImpl implements NoSQL {
private ConcurrentMap<Class<? extends NoSQLObject>, ObjectInfo> objectInfoCache =
new ConcurrentHashMap<Class<? extends NoSQLObject>, ObjectInfo>();
public MongoDBImpl(DB database, boolean removeAllObjectsAtStartup, Class<? extends NoSQLObject>[] managedDataTypes) {
this.database = database;
typeConverter = new TypeConverter();
typeConverter.addConverter(new BasicDBListToStringArrayConverter());
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));
for (Class<? extends NoSQLObject> type : managedDataTypes) {
typeConverter.addConverter(new NoSQLObjectConverter(this, typeConverter, type));
getObjectInfo(type);
typeConverter.addAppObjectConverter(new NoSQLObjectConverter(this, typeConverter, type));
typeConverter.addDBObjectConverter(new BasicDBObjectConverter(this, typeConverter, type));
}
if (removeAllObjectsAtStartup) {
@ -55,10 +75,10 @@ public class MongoDBImpl implements NoSQL {
ObjectInfo objectInfo = getObjectInfo(type);
String collectionName = objectInfo.getDbCollectionName();
if (collectionName != null) {
logger.debug("Removing all objects of type " + type);
logger.debug("Dropping collection " + collectionName);
DBCollection dbCollection = this.database.getCollection(collectionName);
dbCollection.remove(new BasicDBObject());
dbCollection.drop();
} else {
logger.debug("Skip removing objects of type " + type + " as it doesn't have it's own collection");
}
@ -67,6 +87,7 @@ public class MongoDBImpl implements NoSQL {
}
}
@Override
public void saveObject(NoSQLObject object) {
Class<? extends NoSQLObject> clazz = object.getClass();
@ -90,12 +111,12 @@ public class MongoDBImpl implements NoSQL {
oidProperty.setValue(object, dbObject.getString("_id"));
}
} else {
BasicDBObject setCommand = new BasicDBObject("$set", dbObject);
BasicDBObject query = new BasicDBObject("_id", new ObjectId(currentId));
dbCollection.update(query, setCommand);
dbCollection.update(query, dbObject);
}
}
@Override
public <T extends NoSQLObject> T loadObject(Class<T> type, String oid) {
DBCollection dbCollection = getDBCollectionForType(type);
@ -106,6 +127,7 @@ public class MongoDBImpl implements NoSQL {
return typeConverter.convertDBObjectToApplicationObject(dbObject, type);
}
@Override
public <T extends NoSQLObject> T loadSingleObject(Class<T> type, NoSQLQuery query) {
List<T> result = loadObjects(type, query);
@ -119,6 +141,7 @@ public class MongoDBImpl implements NoSQL {
}
}
@Override
public <T extends NoSQLObject> List<T> loadObjects(Class<T> type, NoSQLQuery query) {
DBCollection dbCollection = getDBCollectionForType(type);
@ -129,6 +152,7 @@ public class MongoDBImpl implements NoSQL {
return convertCursor(type, cursor);
}
@Override
public void removeObject(NoSQLObject object) {
Class<? extends NoSQLObject> type = object.getClass();
@ -140,6 +164,7 @@ public class MongoDBImpl implements NoSQL {
removeObject(type, oid);
}
@Override
public void removeObject(Class<? extends NoSQLObject> type, String oid) {
NoSQLObject found = loadObject(type, oid);
@ -155,6 +180,7 @@ public class MongoDBImpl implements NoSQL {
}
}
@Override
public void removeObjects(Class<? extends NoSQLObject> type, NoSQLQuery query) {
List<? extends NoSQLObject> foundObjects = loadObjects(type, query);
@ -172,14 +198,81 @@ public class MongoDBImpl implements NoSQL {
}
}
@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 != 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 addConverter(Converter<?, ?> converter) {
typeConverter.addConverter(converter);
public void addAppObjectConverter(Converter<?, ?> converter) {
typeConverter.addAppObjectConverter(converter);
}
public void addDBObjectConverter(Converter<?, ?> converter) {
typeConverter.addDBObjectConverter(converter);
}
public ObjectInfo getObjectInfo(Class<? extends NoSQLObject> objectClass) {

View file

@ -1,5 +1,9 @@
package org.keycloak.services.models.nosql.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.services.models.nosql.api.query.NoSQLQueryBuilder;
@ -12,18 +16,17 @@ public class MongoDBQueryBuilder extends NoSQLQueryBuilder {
protected MongoDBQueryBuilder() {};
@Override
public NoSQLQueryBuilder inCondition(String name, Object[] values) {
public NoSQLQueryBuilder inCondition(String name, List<?> values) {
if (values == null) {
values = new Object[0];
values = new LinkedList<Object>();
}
if ("_id".equals(name)) {
// we need to convert Strings to ObjectID
ObjectId[] objIds = new ObjectId[values.length];
for (int i=0 ; i<values.length ; i++) {
String id = values[i].toString();
ObjectId objectId = new ObjectId(id);
objIds[i] = objectId;
List<ObjectId> objIds = new ArrayList<ObjectId>();
for (Object object : values) {
ObjectId objectId = new ObjectId(object.toString());
objIds.add(objectId);
}
values = objIds;
}

View file

@ -1,6 +1,10 @@
package org.keycloak.services.models.nosql.impl;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.keycloak.services.models.nosql.api.NoSQLObject;
import org.picketlink.common.properties.Property;
@ -16,13 +20,18 @@ public class ObjectInfo {
private final Property<String> oidProperty;
private final List<Property<Object>> properties;
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;
this.properties = properties;
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() {
@ -37,17 +46,11 @@ public class ObjectInfo {
return oidProperty;
}
public List<Property<Object>> getProperties() {
return properties;
public Collection<Property<Object>> getProperties() {
return properties.values();
}
public Property<Object> getPropertyByName(String propertyName) {
for (Property<Object> property : properties) {
if (propertyName.equals(property.getName())) {
return property;
}
}
return null;
return properties.get(propertyName);
}
}

View file

@ -1,75 +0,0 @@
package org.keycloak.services.models.nosql.impl;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class Utils {
private Utils() {};
/**
* Add item to the end of array
*
* @param inputArray could be null. In this case, method will return array of length 1 with added item
* @param item must be not-null
* @return array with added item to the end
*/
public static <T> T[] addItemToArray(T[] inputArray, T item) {
if (item == null) {
throw new IllegalArgumentException("item must be non-null");
}
T[] outputArray;
if (inputArray == null) {
outputArray = (T[])Array.newInstance(item.getClass(), 1);
} else {
outputArray = Arrays.copyOf(inputArray, inputArray.length + 1);
}
outputArray[outputArray.length - 1] = item;
return outputArray;
}
/**
* Return true if array contains specified item
* @param array could be null (In this case method always return false)
* @param item can't be null
* @return
*/
public static boolean contains(Object[] array, Object item) {
if (item == null) {
throw new IllegalArgumentException("item must be non-null");
}
if (array != null) {
for (Object current : array) {
if (item.equals(current)) {
return true;
}
}
}
return false;
}
public static <T> T[] removeItemFromArray(T[] inputArray, T item) {
if (item == null) {
throw new IllegalArgumentException("item must be non-null");
}
if (inputArray == null) {
return inputArray;
} else {
T[] outputArray = (T[])Array.newInstance(item.getClass(), inputArray.length - 1);
int counter = 0;
for (T object : inputArray) {
if (!item.equals(object)) {
outputArray[counter++] = object;
}
}
return outputArray;
}
}
}

View file

@ -0,0 +1,64 @@
package org.keycloak.services.models.nosql.impl.types;
import java.util.ArrayList;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import org.keycloak.services.models.nosql.api.types.Converter;
import org.keycloak.services.models.nosql.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 {
return Object.class;
}
}
}

View file

@ -1,41 +0,0 @@
package org.keycloak.services.models.nosql.impl.types;
import com.mongodb.BasicDBList;
import org.keycloak.services.models.nosql.api.types.Converter;
/**
* Convert BasicDBList to String[] and viceversa (T needs to be declared as Object as Array is not possible here :/ )
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class BasicDBListToStringArrayConverter implements Converter<Object, BasicDBList> {
private static final String[] PLACEHOLDER = new String[] {};
@Override
public String[] convertDBObjectToApplicationObject(BasicDBList dbObject) {
return dbObject.toArray(PLACEHOLDER);
}
@Override
public BasicDBList convertApplicationObjectToDBObject(Object applicationObject) {
BasicDBList list = new BasicDBList();
String[] array = (String[])applicationObject;
for (String key : array) {
list.add(key);
}
return list;
}
@Override
public Class<?> getApplicationObjectType() {
return PLACEHOLDER.getClass();
}
@Override
public Class<BasicDBList> getDBObjectType() {
return BasicDBList.class;
}
}

View file

@ -0,0 +1,102 @@
package org.keycloak.services.models.nosql.impl.types;
import com.mongodb.BasicDBObject;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.services.models.nosql.api.AttributedNoSQLObject;
import org.keycloak.services.models.nosql.api.NoSQLObject;
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.MongoDBImpl;
import org.keycloak.services.models.nosql.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;
}
}

View file

@ -0,0 +1,52 @@
package org.keycloak.services.models.nosql.impl.types;
import java.util.List;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import org.keycloak.services.models.nosql.api.types.Converter;
import org.keycloak.services.models.nosql.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;
}
}

View file

@ -1,5 +1,6 @@
package org.keycloak.services.models.nosql.impl.types;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -29,84 +30,18 @@ public class NoSQLObjectConverter<T extends NoSQLObject> implements Converter<T,
}
@Override
public T convertDBObjectToApplicationObject(BasicDBObject dbObject) {
if (dbObject == null) {
return null;
}
ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(expectedNoSQLObjectType);
T 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
// TODO: logging
// logger.warn("Property with key " + key + " not known for type " + type);
System.err.println("Property with key " + key + " not known for type " + expectedNoSQLObjectType);
}
}
return object;
}
private void setPropertyValue(NoSQLObject object, Object valueFromDB, Property property) {
Class<?> expectedType = property.getJavaClass();
Class actualType = valueFromDB != null ? valueFromDB.getClass() : expectedType;
// handle primitives
expectedType = Types.boxedClass(expectedType);
actualType = Types.boxedClass(actualType);
if (actualType.isAssignableFrom(expectedType)) {
property.setValue(object, valueFromDB);
} else {
// we need to convert
Object convertedValue = typeConverter.convertDBObjectToApplicationObject(valueFromDB, expectedType);
property.setValue(object, convertedValue);
}
}
@Override
public BasicDBObject convertApplicationObjectToDBObject(T applicationObject) {
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();
List<Property<Object>> props = objectInfo.getProperties();
Collection<Property<Object>> props = objectInfo.getProperties();
for (Property<Object> property : props) {
String propName = property.getName();
Object propValue = property.getValue(applicationObject);
// Check if we have noSQLObject, which is indication that we need to convert recursively
if (propValue instanceof NoSQLObject) {
propValue = typeConverter.convertApplicationObjectToDBObject(propValue, BasicDBObject.class);
}
dbObject.append(propName, propValue);
Object dbValue = propValue == null ? null : typeConverter.convertApplicationObjectToDBObject(propValue, Types.boxedClass(property.getJavaClass()));
dbObject.put(propName, dbValue);
}
// Adding attributes
@ -122,12 +57,12 @@ public class NoSQLObjectConverter<T extends NoSQLObject> implements Converter<T,
}
@Override
public Class<T> getApplicationObjectType() {
public Class<? extends T> getConverterObjectType() {
return expectedNoSQLObjectType;
}
@Override
public Class<BasicDBObject> getDBObjectType() {
public Class<BasicDBObject> getExpectedReturnType() {
return BasicDBObject.class;
}
}

View file

@ -0,0 +1,30 @@
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 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;
}
}

View file

@ -10,9 +10,6 @@ import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.models.nosql.api.NoSQL;
import org.keycloak.services.models.nosql.api.query.NoSQLQuery;
import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder;
import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder;
import org.keycloak.services.models.nosql.impl.Utils;
import org.keycloak.services.models.nosql.keycloak.data.ApplicationData;
import org.keycloak.services.models.nosql.keycloak.data.RoleData;
import org.keycloak.services.models.nosql.keycloak.data.UserData;
@ -138,7 +135,7 @@ public class ApplicationAdapter implements ApplicationModel {
@Override
public Set<String> getRoleMappings(UserModel user) {
UserData userData = ((UserAdapter)user).getUser();
String[] roleIds = userData.getRoleIds();
List<String> roleIds = userData.getRoleIds();
Set<String> result = new HashSet<String>();
@ -146,7 +143,7 @@ public class ApplicationAdapter implements ApplicationModel {
.inCondition("_id", roleIds)
.build();
List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
// TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically...
// 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());
@ -168,19 +165,13 @@ public class ApplicationAdapter implements ApplicationModel {
@Override
public void addScope(UserModel agent, RoleModel role) {
UserData userData = ((UserAdapter)agent).getUser();
RoleData roleData = ((RoleAdapter)role).getRole();
String[] scopeIds = userData.getScopeIds();
scopeIds = Utils.addItemToArray(scopeIds, roleData.getId());
userData.setScopeIds(scopeIds);
noSQL.saveObject(userData);
noSQL.pushItemToList(userData, "scopeIds", role.getId());
}
@Override
public Set<String> getScope(UserModel agent) {
UserData userData = ((UserAdapter)agent).getUser();
String[] scopeIds = userData.getScopeIds();
List<String> scopeIds = userData.getScopeIds();
Set<String> result = new HashSet<String>();
@ -188,7 +179,7 @@ public class ApplicationAdapter implements ApplicationModel {
.inCondition("_id", scopeIds)
.build();
List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
// TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically...
// 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());

View file

@ -23,7 +23,6 @@ import org.keycloak.services.models.UserCredentialModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.models.nosql.api.NoSQL;
import org.keycloak.services.models.nosql.api.query.NoSQLQuery;
import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder;
import org.keycloak.services.models.nosql.keycloak.credentials.PasswordCredentialHandler;
import org.keycloak.services.models.nosql.keycloak.credentials.TOTPCredentialHandler;
import org.keycloak.services.models.nosql.keycloak.data.ApplicationData;
@ -32,11 +31,7 @@ import org.keycloak.services.models.nosql.keycloak.data.RequiredCredentialData;
import org.keycloak.services.models.nosql.keycloak.data.RoleData;
import org.keycloak.services.models.nosql.keycloak.data.SocialLinkData;
import org.keycloak.services.models.nosql.keycloak.data.UserData;
import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder;
import org.keycloak.services.models.nosql.impl.Utils;
import org.keycloak.services.models.picketlink.relationships.ResourceRelationship;
import org.picketlink.idm.credential.Credentials;
import org.picketlink.idm.query.RelationshipQuery;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -323,7 +318,7 @@ public class RealmAdapter implements RealmModel {
@Override
public List<RoleModel> getDefaultRoles() {
String[] defaultRoles = realm.getDefaultRoles();
List<String> defaultRoles = realm.getDefaultRoles();
NoSQLQuery query = noSQL.createQueryBuilder()
.inCondition("_id", defaultRoles)
@ -344,25 +339,20 @@ public class RealmAdapter implements RealmModel {
role = addRole(name);
}
String[] defaultRoles = realm.getDefaultRoles();
String[] roleIds = Utils.addItemToArray(defaultRoles, role.getId());
realm.setDefaultRoles(roleIds);
updateRealm();
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
String[] roleIds = new String[defaultRoles.length];
for (int i=0 ; i<defaultRoles.length ; i++) {
String roleName = defaultRoles[i];
List<String> roleIds = new ArrayList<String>();
for (String roleName : defaultRoles) {
RoleModel role = getRole(roleName);
if (role == null) {
role = addRole(roleName);
}
roleIds[i] = role.getId();
roleIds.add(role.getId());
}
realm.setDefaultRoles(roleIds);
@ -425,7 +415,7 @@ public class RealmAdapter implements RealmModel {
public boolean hasRole(UserModel user, RoleModel role) {
UserData userData = ((UserAdapter)user).getUser();
String[] roleIds = userData.getRoleIds();
List<String> roleIds = userData.getRoleIds();
String roleId = role.getId();
if (roleIds != null) {
for (String currentId : roleIds) {
@ -440,19 +430,13 @@ public class RealmAdapter implements RealmModel {
@Override
public void grantRole(UserModel user, RoleModel role) {
UserData userData = ((UserAdapter)user).getUser();
RoleData roleData = ((RoleAdapter)role).getRole();
String[] roleIds = userData.getRoleIds();
roleIds = Utils.addItemToArray(roleIds, roleData.getId());
userData.setRoleIds(roleIds);
noSQL.saveObject(userData);
noSQL.pushItemToList(userData, "roleIds", role.getId());
}
@Override
public Set<String> getRoleMappings(UserModel user) {
UserData userData = ((UserAdapter)user).getUser();
String[] roleIds = userData.getRoleIds();
List<String> roleIds = userData.getRoleIds();
Set<String> result = new HashSet<String>();
@ -460,7 +444,7 @@ public class RealmAdapter implements RealmModel {
.inCondition("_id", roleIds)
.build();
List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
// TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically...
// 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());
@ -476,19 +460,14 @@ public class RealmAdapter implements RealmModel {
if (role == null) {
throw new RuntimeException("Role not found");
}
RoleData roleData = role.getRole();
String[] scopeIds = userData.getScopeIds();
scopeIds = Utils.addItemToArray(scopeIds, roleData.getId());
userData.setScopeIds(scopeIds);
noSQL.saveObject(userData);
noSQL.pushItemToList(userData, "scopeIds", role.getId());
}
@Override
public Set<String> getScope(UserModel agent) {
UserData userData = ((UserAdapter)agent).getUser();
String[] scopeIds = userData.getScopeIds();
List<String> scopeIds = userData.getScopeIds();
Set<String> result = new HashSet<String>();
@ -496,7 +475,7 @@ public class RealmAdapter implements RealmModel {
.inCondition("_id", scopeIds)
.build();
List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
// TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically...
// 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());
@ -507,20 +486,16 @@ public class RealmAdapter implements RealmModel {
@Override
public boolean isRealmAdmin(UserModel agent) {
String[] realmAdmins = realm.getRealmAdmins();
List<String> realmAdmins = realm.getRealmAdmins();
String userId = ((UserAdapter)agent).getUser().getId();
return Utils.contains(realmAdmins, userId);
return realmAdmins.contains(userId);
}
@Override
public void addRealmAdmin(UserModel agent) {
UserData userData = ((UserAdapter)agent).getUser();
String[] currentAdmins = realm.getRealmAdmins();
String[] newAdmins = Utils.addItemToArray(currentAdmins, userData.getId());
realm.setRealmAdmins(newAdmins);
updateRealm();
noSQL.pushItemToList(realm, "realmAdmins", userData.getId());
}
@Override

View file

@ -1,6 +1,7 @@
package org.keycloak.services.models.nosql.keycloak.data;
import java.security.SecureRandom;
import java.util.List;
import java.util.Random;
import java.util.UUID;
@ -32,8 +33,8 @@ public class RealmData implements NoSQLObject {
private String publicKeyPem;
private String privateKeyPem;
private String[] defaultRoles;
private String[] realmAdmins;
private List<String> defaultRoles;
private List<String> realmAdmins;
@NoSQLId
public String getOid() {
@ -44,7 +45,6 @@ public class RealmData implements NoSQLObject {
this.oid = oid;
}
// TODO: Is ID really needed? It seems that it exists just to workaround picketlink...
@NoSQLField
public String getId() {
return id;
@ -154,20 +154,20 @@ public class RealmData implements NoSQLObject {
}
@NoSQLField
public String[] getDefaultRoles() {
public List<String> getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(String[] defaultRoles) {
public void setDefaultRoles(List<String> defaultRoles) {
this.defaultRoles = defaultRoles;
}
@NoSQLField
public String[] getRealmAdmins() {
public List<String> getRealmAdmins() {
return realmAdmins;
}
public void setRealmAdmins(String[] realmAdmins) {
public void setRealmAdmins(List<String> realmAdmins) {
this.realmAdmins = realmAdmins;
}

View file

@ -9,7 +9,6 @@ import org.keycloak.services.models.nosql.api.NoSQLField;
import org.keycloak.services.models.nosql.api.NoSQLId;
import org.keycloak.services.models.nosql.api.NoSQLObject;
import org.keycloak.services.models.nosql.api.query.NoSQLQuery;
import org.keycloak.services.models.nosql.impl.Utils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -81,10 +80,7 @@ public class RoleData implements NoSQLObject {
List<UserData> users = noSQL.loadObjects(UserData.class, query);
for (UserData user : users) {
logger.info("Removing role " + getName() + " from user " + user.getLoginName());
String[] roleIds = user.getRoleIds();
String[] newRoleIds = Utils.removeItemFromArray(roleIds, getId());
user.setRoleIds(newRoleIds);
noSQL.saveObject(user);
noSQL.pullItemFromList(user, "roleIds", getId());
}
// Remove this scope from all users, which has it
@ -95,10 +91,7 @@ public class RoleData implements NoSQLObject {
users = noSQL.loadObjects(UserData.class, query);
for (UserData user : users) {
logger.info("Removing scope " + getName() + " from user " + user.getLoginName());
String[] scopeIds = user.getScopeIds();
String[] newScopeIds = Utils.removeItemFromArray(scopeIds, getId());
user.setScopeIds(newScopeIds);
noSQL.saveObject(user);
noSQL.pullItemFromList(user, "scopeIds", getId());
}
}

View file

@ -1,5 +1,8 @@
package org.keycloak.services.models.nosql.keycloak.data;
import java.util.List;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.services.models.nosql.api.AbstractAttributedNoSQLObject;
import org.keycloak.services.models.nosql.api.NoSQL;
import org.keycloak.services.models.nosql.api.NoSQLCollection;
@ -14,6 +17,8 @@ import org.keycloak.services.models.nosql.keycloak.data.credentials.PasswordData
@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;
@ -23,8 +28,8 @@ public class UserData extends AbstractAttributedNoSQLObject {
private String realmId;
private String[] roleIds;
private String[] scopeIds;
private List<String> roleIds;
private List<String> scopeIds;
@NoSQLId
public String getId() {
@ -90,20 +95,20 @@ public class UserData extends AbstractAttributedNoSQLObject {
}
@NoSQLField
public String[] getRoleIds() {
public List<String> getRoleIds() {
return roleIds;
}
public void setRoleIds(String[] roleIds) {
public void setRoleIds(List<String> roleIds) {
this.roleIds = roleIds;
}
@NoSQLField
public String[] getScopeIds() {
public List<String> getScopeIds() {
return scopeIds;
}
public void setScopeIds(String[] scopeIds) {
public void setScopeIds(List<String> scopeIds) {
this.scopeIds = scopeIds;
}
@ -116,5 +121,16 @@ public class UserData extends AbstractAttributedNoSQLObject {
// 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());
}
}
}

View file

@ -0,0 +1,43 @@
package org.keycloak.test.nosql;
import java.util.List;
import org.keycloak.services.models.nosql.api.AbstractNoSQLObject;
import org.keycloak.services.models.nosql.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;
}
}

View file

@ -0,0 +1,103 @@
package org.keycloak.test.nosql;
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.junit.Test;
import org.keycloak.services.models.nosql.api.NoSQL;
import org.keycloak.services.models.nosql.api.NoSQLObject;
import org.keycloak.services.models.nosql.api.query.NoSQLQuery;
import org.keycloak.services.models.nosql.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);
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);
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());
}
}

View file

@ -0,0 +1,66 @@
package org.keycloak.test.nosql;
import java.util.List;
import org.keycloak.services.models.nosql.api.AbstractNoSQLObject;
import org.keycloak.services.models.nosql.api.NoSQLCollection;
import org.keycloak.services.models.nosql.api.NoSQLField;
import org.keycloak.services.models.nosql.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;
@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 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;
}
}