Added first version of NoSQL api and MongoDBImpl implementation
This commit is contained in:
parent
7a09d38ff7
commit
0acc9e978a
13 changed files with 533 additions and 3 deletions
5
pom.xml
5
pom.xml
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 + "]";
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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?
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue