keycloak-memoizeit
Changes
services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java 64(+64 -0)
services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java 41(+0 -41)
services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBObjectConverter.java 102(+102 -0)
services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java 79(+7 -72)
services/src/main/java/org/keycloak/services/models/nosql/impl/types/SimpleConverter.java 30(+30 -0)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java 19(+5 -14)
Details
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java
index 1b28fd1..f302567 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java
@@ -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);
}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java
index 341a115..9f2c383 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java
@@ -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);
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java
index 221db2b..3740201 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java
@@ -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();
}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java
index 3368e4f..fd2020a 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java
@@ -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<?,?>>>();
- 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 application objects to DB objects
+ *
+ * @param converter
+ */
+ public void addAppObjectConverter(Converter<?, ?> converter) {
+ appObjectConverters.put(converter.getConverterObjectType(), 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);
+ /**
+ * 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);
}
- 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);
+
+ 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);
+ }
+ }
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;
+ 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;
+ }
+ }
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java
index 0385aee..6862a0c 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java
@@ -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) {
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java
index 94b6196..80f5efb 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java
@@ -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;
}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java
index 27f782c..da9d279 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java
@@ -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);
}
}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java
new file mode 100644
index 0000000..87941d3
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java
@@ -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;
+ }
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBObjectConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBObjectConverter.java
new file mode 100644
index 0000000..4257fb4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBObjectConverter.java
@@ -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;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ListConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ListConverter.java
new file mode 100644
index 0000000..a9685ab
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ListConverter.java
@@ -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;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java
index a3974b3..c244607 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java
@@ -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;
}
}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/SimpleConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/SimpleConverter.java
new file mode 100644
index 0000000..8dc1b62
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/SimpleConverter.java
@@ -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;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java
index dd10400..4d7a564 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java
@@ -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());
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java
index f7e3e83..2165165 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java
@@ -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
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java
index 42f8ab2..8152c5b 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java
@@ -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;
}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java
index 89138e6..49483a1 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java
@@ -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());
}
}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java
index c2321fb..83c7151 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java
@@ -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());
+ }
}
}
diff --git a/services/src/test/java/org/keycloak/test/nosql/Address.java b/services/src/test/java/org/keycloak/test/nosql/Address.java
new file mode 100644
index 0000000..8b56c59
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/nosql/Address.java
@@ -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;
+ }
+}
diff --git a/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java b/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java
new file mode 100644
index 0000000..276ea3c
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java
@@ -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());
+ }
+}
diff --git a/services/src/test/java/org/keycloak/test/nosql/Person.java b/services/src/test/java/org/keycloak/test/nosql/Person.java
new file mode 100644
index 0000000..fa091fa
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/nosql/Person.java
@@ -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;
+ }
+}