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();
+    }
+}