Added first version of NoSQL api and MongoDBImpl implementation

This commit is contained in:
mposolda 2013-08-30 17:38:54 +02:00
parent 7a09d38ff7
commit 0acc9e978a
13 changed files with 533 additions and 3 deletions

View file

@ -249,6 +249,11 @@
<artifactId>selenium-chrome-driver</artifactId>
<version>2.35.0</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>2.11.2</version>
</dependency>
</dependencies>
</dependencyManagement>

View file

@ -144,6 +144,14 @@
<artifactId>jackson-xc</artifactId>
<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>

View file

@ -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 + "]";
}
}

View file

@ -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;
}
}

View file

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

View file

@ -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);
}

View file

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

View file

@ -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?
}

View file

@ -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 {
}

View file

@ -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 {
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

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