keycloak-memoizeit
Details
pom.xml 11(+8 -3)
diff --git a/pom.xml b/pom.xml
index d08e744..99a90cd 100755
--- a/pom.xml
+++ b/pom.xml
@@ -236,9 +236,9 @@
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>1.3.1b</version>
- </dependency>
+ </dependency>
- <!-- Selenium -->
+ <!-- Selenium -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
@@ -248,7 +248,12 @@
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId>
<version>2.35.0</version>
- </dependency>
+ </dependency>
+ <dependency>
+ <groupId>org.mongodb</groupId>
+ <artifactId>mongo-java-driver</artifactId>
+ <version>2.11.2</version>
+ </dependency>
</dependencies>
</dependencyManagement>
services/pom.xml 8(+8 -0)
diff --git a/services/pom.xml b/services/pom.xml
index 4d8075f..7c38f45 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -145,6 +145,14 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>org.picketlink</groupId>
+ <artifactId>picketlink-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mongodb</groupId>
+ <artifactId>mongo-java-driver</artifactId>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLRealm.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLRealm.java
new file mode 100644
index 0000000..c061f8b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLRealm.java
@@ -0,0 +1,49 @@
+package org.keycloak.services.models.nosql.adapters;
+
+import org.keycloak.services.models.nosql.api.NoSQLCollection;
+import org.keycloak.services.models.nosql.api.NoSQLField;
+import org.keycloak.services.models.nosql.api.NoSQLId;
+import org.keycloak.services.models.nosql.api.NoSQLObject;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "realms")
+public class NoSQLRealm implements NoSQLObject {
+
+ private String oid;
+ private String prop1;
+ private Integer prop2;
+
+ @NoSQLId
+ public String getOid() {
+ return oid;
+ }
+
+ public void setOid(String oid) {
+ this.oid = oid;
+ }
+
+ @NoSQLField(fieldName = "property1")
+ public String getProp1() {
+ return prop1;
+ }
+
+ public void setProp1(String prop1) {
+ this.prop1 = prop1;
+ }
+
+ @NoSQLField(fieldName = "property2")
+ public Integer getProp2() {
+ return prop2;
+ }
+
+ public void setProp2(Integer prop2) {
+ this.prop2 = prop2;
+ }
+
+ @Override
+ public String toString() {
+ return "NoSQLRealm [ oid=" + oid + ", prop1=" + prop1 + ", prop2=" + prop2 + "]";
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLUser.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLUser.java
new file mode 100644
index 0000000..5c316ff
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLUser.java
@@ -0,0 +1,46 @@
+package org.keycloak.services.models.nosql.adapters;
+
+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 = "users")
+public class NoSQLUser {
+
+ @NoSQLId
+ private String oid;
+
+ private String username;
+
+ private String realmId;
+
+ @NoSQLId
+ public String getOid() {
+ return oid;
+ }
+
+ public void setOid(String oid) {
+ this.oid = oid;
+ }
+
+ @NoSQLField
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ @NoSQLField(fieldName = "realm_id")
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/AttributedNoSQLObject.java b/services/src/main/java/org/keycloak/services/models/nosql/api/AttributedNoSQLObject.java
new file mode 100644
index 0000000..f750e82
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/AttributedNoSQLObject.java
@@ -0,0 +1,17 @@
+package org.keycloak.services.models.nosql.api;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface AttributedNoSQLObject extends NoSQLObject {
+
+ void setAttribute(String name, String value);
+
+ void removeAttribute(String name);
+
+ String getAttribute(String name);
+
+ Map<String, String> getAttributes();
+}
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
new file mode 100644
index 0000000..dc16bb6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java
@@ -0,0 +1,26 @@
+package org.keycloak.services.models.nosql.api;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface NoSQL {
+
+ /**
+ * Insert object if it's oid is null. Otherwise update
+ */
+ void saveObject(NoSQLObject object);
+
+ <T extends NoSQLObject> T loadObject(Class<T> type, String oid);
+
+ <T extends NoSQLObject> List<T> loadObjects(Class<T> type, Map<String, Object> queryAttributes);
+
+ // Object must have filled oid
+ void removeObject(NoSQLObject object);
+
+ void removeObject(Class<? extends NoSQLObject> type, String oid);
+
+ void removeObjects(Class<? extends NoSQLObject> type, Map<String, Object> queryAttributes);
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLCollection.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLCollection.java
new file mode 100644
index 0000000..ff41188
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLCollection.java
@@ -0,0 +1,21 @@
+package org.keycloak.services.models.nosql.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Target({TYPE})
+@Documented
+@Retention(RUNTIME)
+@Inherited
+public @interface NoSQLCollection {
+
+ String collectionName();
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java
new file mode 100644
index 0000000..c3e0586
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java
@@ -0,0 +1,22 @@
+package org.keycloak.services.models.nosql.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Target({METHOD, FIELD})
+@Documented
+@Retention(RUNTIME)
+public @interface NoSQLField {
+
+ String fieldName() default "";
+
+ // TODO: add lazy loading?
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLId.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLId.java
new file mode 100644
index 0000000..0cbca85
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLId.java
@@ -0,0 +1,18 @@
+package org.keycloak.services.models.nosql.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Target({METHOD, FIELD})
+@Documented
+@Retention(RUNTIME)
+public @interface NoSQLId {
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java
new file mode 100644
index 0000000..1f430b6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java
@@ -0,0 +1,9 @@
+package org.keycloak.services.models.nosql.api;
+
+/**
+ * Just marker interface
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface NoSQLObject {
+}
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
new file mode 100644
index 0000000..bbdd846
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java
@@ -0,0 +1,203 @@
+package org.keycloak.services.models.nosql.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DB;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+import org.bson.types.ObjectId;
+import org.jboss.resteasy.logging.Logger;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.models.nosql.api.AttributedNoSQLObject;
+import org.keycloak.services.models.nosql.api.NoSQL;
+import org.keycloak.services.models.nosql.api.NoSQLCollection;
+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.picketlink.common.properties.Property;
+import org.picketlink.common.properties.query.AnnotatedPropertyCriteria;
+import org.picketlink.common.properties.query.PropertyQueries;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MongoDBImpl implements NoSQL {
+
+ private final DB database;
+ // private static final Logger logger = Logger.getLogger(MongoDBImpl.class);
+
+ public MongoDBImpl(DB database) {
+ this.database = database;
+ }
+
+ private ConcurrentMap<Class<? extends NoSQLObject>, ObjectInfo<? extends NoSQLObject>> objectInfoCache =
+ new ConcurrentHashMap<Class<? extends NoSQLObject>, ObjectInfo<? extends NoSQLObject>>();
+
+
+ @Override
+ public void saveObject(NoSQLObject object) {
+ Class<?> clazz = object.getClass();
+
+ // Find annotations for ID, for all the properties and for the name of the collection.
+ ObjectInfo objectInfo = getObjectInfo(clazz);
+
+ // 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();
+ for (Property<Object> property : props) {
+ String propName = property.getName();
+ Object propValue = property.getValue(object);
+
+
+ dbObject.append(propName, propValue);
+
+ // Adding attributes
+ if (object instanceof AttributedNoSQLObject) {
+ AttributedNoSQLObject attributedObject = (AttributedNoSQLObject)object;
+ Map<String, String> attributes = attributedObject.getAttributes();
+ for (Map.Entry<String, String> attribute : attributes.entrySet()) {
+ dbObject.append(attribute.getKey(), attribute.getValue());
+ }
+ }
+ }
+
+ DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName());
+
+ // Decide if we should insert or update (based on presence of oid property in original object)
+ Property<String> oidProperty = objectInfo.getOidProperty();
+ String currentId = oidProperty.getValue(object);
+ if (currentId == null) {
+ dbCollection.insert(dbObject);
+
+ // Add oid to value of given object
+ 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);
+ }
+ }
+
+ @Override
+ public <T extends NoSQLObject> T loadObject(Class<T> type, String oid) {
+ ObjectInfo<T> objectInfo = getObjectInfo(type);
+ DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName());
+
+ BasicDBObject idQuery = new BasicDBObject("_id", new ObjectId(oid));
+ DBObject dbObject = dbCollection.findOne(idQuery);
+
+ return convertObject(type, dbObject);
+ }
+
+ @Override
+ public <T extends NoSQLObject> List<T> loadObjects(Class<T> type, Map<String, Object> queryAttributes) {
+ ObjectInfo<T> objectInfo = getObjectInfo(type);
+ DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName());
+
+ BasicDBObject query = new BasicDBObject();
+ for (Map.Entry<String, Object> queryAttr : queryAttributes.entrySet()) {
+ query.append(queryAttr.getKey(), queryAttr.getValue());
+ }
+ DBCursor cursor = dbCollection.find(query);
+
+ return convertCursor(type, cursor);
+ }
+
+ @Override
+ public void removeObject(NoSQLObject object) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void removeObject(Class<? extends NoSQLObject> type, String oid) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void removeObjects(Class<? extends NoSQLObject> type, Map<String, Object> queryAttributes) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ private <T extends NoSQLObject> ObjectInfo<T> getObjectInfo(Class<?> objectClass) {
+ ObjectInfo<T> objectInfo = (ObjectInfo<T>)objectInfoCache.get(objectClass);
+ if (objectInfo == null) {
+ Property<String> idProperty = PropertyQueries.<String>createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLId.class)).getFirstResult();
+ if (idProperty == null) {
+ throw new IllegalStateException("Class " + objectClass + " doesn't have property with declared annotation " + NoSQLId.class);
+ }
+
+ List<Property<Object>> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLField.class)).getResultList();
+
+ NoSQLCollection classAnnotation = objectClass.getAnnotation(NoSQLCollection.class);
+ if (classAnnotation == null) {
+ throw new IllegalStateException("Class " + objectClass + " doesn't have annotation " + NoSQLCollection.class);
+ }
+
+ String dbCollectionName = classAnnotation.collectionName();
+ objectInfo = new ObjectInfo<T>((Class<T>)objectClass, dbCollectionName, idProperty, properties);
+
+ ObjectInfo existing = objectInfoCache.putIfAbsent((Class<T>)objectClass, objectInfo);
+ if (existing != null) {
+ objectInfo = existing;
+ }
+ }
+
+ return objectInfo;
+ }
+
+
+ private <T extends NoSQLObject> T convertObject(Class<T> type, DBObject dbObject) {
+ ObjectInfo<T> objectInfo = getObjectInfo(type);
+
+ T object;
+ try {
+ object = type.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();
+ idProperty.setValue(object, value.toString());
+
+ } else if ((property = objectInfo.getPropertyByName(key)) != null) {
+ // It's declared property with @DBField annotation
+ property.setValue(object, value);
+
+ } 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 " + type);
+ }
+ }
+
+ return object;
+ }
+
+ private <T extends NoSQLObject> List<T> convertCursor(Class<T> type, DBCursor cursor) {
+ List<T> result = new ArrayList<T>();
+
+ for (DBObject dbObject : cursor) {
+ T converted = convertObject(type, dbObject);
+ result.add(converted);
+ }
+
+ return result;
+ }
+}
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
new file mode 100644
index 0000000..867ac12
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java
@@ -0,0 +1,53 @@
+package org.keycloak.services.models.nosql.impl;
+
+import java.util.List;
+
+import org.keycloak.services.models.nosql.api.NoSQLObject;
+import org.picketlink.common.properties.Property;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+class ObjectInfo<T extends NoSQLObject> {
+
+ private final Class<T> objectClass;
+
+ private final String dbCollectionName;
+
+ private final Property<String> oidProperty;
+
+ private final List<Property<Object>> properties;
+
+ public ObjectInfo(Class<T> objectClass, String dbCollectionName, Property<String> oidProperty, List<Property<Object>> properties) {
+ this.objectClass = objectClass;
+ this.dbCollectionName = dbCollectionName;
+ this.oidProperty = oidProperty;
+ this.properties = properties;
+ }
+
+ public Class<T> getObjectClass() {
+ return objectClass;
+ }
+
+ public String getDbCollectionName() {
+ return dbCollectionName;
+ }
+
+ public Property<String> getOidProperty() {
+ return oidProperty;
+ }
+
+ public List<Property<Object>> getProperties() {
+ return properties;
+ }
+
+ public Property<Object> getPropertyByName(String propertyName) {
+ for (Property<Object> property : properties) {
+ if (propertyName.equals(property.getName())) {
+ return property;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/Test.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/Test.java
new file mode 100644
index 0000000..375418a
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/Test.java
@@ -0,0 +1,53 @@
+package org.keycloak.services.models.nosql.impl;
+
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.mongodb.DB;
+import com.mongodb.MongoClient;
+import org.keycloak.services.models.nosql.adapters.NoSQLRealm;
+
+/**
+ * TODO: delete
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class Test {
+
+ public static void main(String[] args) throws UnknownHostException {
+ MongoClient mongoClient = new MongoClient( "localhost" , 27017 );
+ DB javaDB = mongoClient.getDB("java");
+
+ MongoDBImpl test = new MongoDBImpl(javaDB);
+ NoSQLRealm realm = new NoSQLRealm();
+ realm.setOid("522085fc31dab908ec31c0cb");
+ realm.setProp1("something1");
+ realm.setProp2(12);
+ test.saveObject(realm);
+ System.out.println(realm.getOid());
+
+ realm = test.loadObject(NoSQLRealm.class, "522085fc31dab908ec31c0cb");
+ System.out.println("Loaded realm: " + realm);
+
+ Map<String, Object> query = new HashMap<String, Object>();
+ query.put("prop1", "sm");
+ List<NoSQLRealm> queryResults = test.loadObjects(NoSQLRealm.class, query);
+ System.out.println("results1: " + queryResults);
+
+ query.put("prop1", "something2");
+ queryResults = test.loadObjects(NoSQLRealm.class, query);
+ System.out.println("results2: " + queryResults);
+
+ query.put("prop2", 12);
+ queryResults = test.loadObjects(NoSQLRealm.class, query);
+ System.out.println("results3: " + queryResults);
+
+ query.put("prop1", "something1");
+ queryResults = test.loadObjects(NoSQLRealm.class, query);
+ System.out.println("results4: " + queryResults);
+
+ mongoClient.close();
+ }
+}