Mongo: Remove realmAdmins when realm is removed. Refactored Converters to support list of embedded objects
This commit is contained in:
parent
be48672ba6
commit
4db738689f
23 changed files with 741 additions and 350 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
43
services/src/test/java/org/keycloak/test/nosql/Address.java
Normal file
43
services/src/test/java/org/keycloak/test/nosql/Address.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
66
services/src/test/java/org/keycloak/test/nosql/Person.java
Normal file
66
services/src/test/java/org/keycloak/test/nosql/Person.java
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue