diff --git a/model/api/pom.xml b/model/api/pom.xml
index 2c64294f4a..a5260d49d5 100755
--- a/model/api/pom.xml
+++ b/model/api/pom.xml
@@ -17,6 +17,17 @@
net.iharder
base64
+
+ org.bouncycastle
+ bcprov-jdk16
+ provided
+
+
+ org.keycloak
+ keycloak-core
+ ${project.version}
+ provided
+
junit
junit
diff --git a/model/api/src/main/java/org/keycloak/models/IdGenerator.java b/model/api/src/main/java/org/keycloak/models/IdGenerator.java
deleted file mode 100755
index 3a1b028200..0000000000
--- a/model/api/src/main/java/org/keycloak/models/IdGenerator.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.keycloak.models;
-
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * @author Bill Burke
- * @version $Revision: 1 $
- */
-public class IdGenerator {
- private static AtomicLong counter = new AtomicLong(1);
- public static String generateId() {
- return counter.getAndIncrement() + "-" + System.currentTimeMillis();
- }
-
-}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
new file mode 100644
index 0000000000..4582a03274
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -0,0 +1,86 @@
+package org.keycloak.models.utils;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.bouncycastle.openssl.PEMWriter;
+import org.keycloak.models.RoleModel;
+import org.keycloak.util.PemUtils;
+
+/**
+ * Set of helper methods, which are useful in various model implementations.
+ *
+ * @author Marek Posolda
+ */
+public final class KeycloakModelUtils {
+
+ private KeycloakModelUtils() {
+ }
+
+ private static AtomicLong counter = new AtomicLong(1);
+
+ public static String generateId() {
+ return counter.getAndIncrement() + "-" + System.currentTimeMillis();
+ }
+
+ public static PublicKey getPublicKey(String publicKeyPem) {
+ if (publicKeyPem != null) {
+ try {
+ return PemUtils.decodePublicKey(publicKeyPem);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ return null;
+ }
+ }
+
+ public static PrivateKey getPrivateKey(String privateKeyPem) {
+ if (privateKeyPem != null) {
+ try {
+ return PemUtils.decodePrivateKey(privateKeyPem);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return null;
+ }
+
+ public static String getPemFromKey(Key key) {
+ StringWriter writer = new StringWriter();
+ PEMWriter pemWriter = new PEMWriter(writer);
+ try {
+ pemWriter.writeObject(key);
+ pemWriter.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ String s = writer.toString();
+ return PemUtils.removeBeginEnd(s);
+ }
+
+ /**
+ * Deep search if given role is descendant of composite role
+ *
+ * @param role role to check
+ * @param composite composite role
+ * @param visited set of already visited roles (used for recursion)
+ * @return true if "role" is descendant of "composite"
+ */
+ public static boolean searchFor(RoleModel role, RoleModel composite, Set visited) {
+ if (visited.contains(composite)) return false;
+ visited.add(composite);
+ Set composites = composite.getComposites();
+ if (composites.contains(role)) return true;
+ for (RoleModel contained : composites) {
+ if (!contained.isComposite()) continue;
+ if (searchFor(role, contained, visited)) return true;
+ }
+ return false;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java
deleted file mode 100644
index 57285db004..0000000000
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.keycloak.models.utils;
-
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * @author Marek Posolda
- */
-public final class KeycloakSessionUtils {
-
- private KeycloakSessionUtils() {
- }
-
- private static AtomicLong counter = new AtomicLong(1);
-
- public static String generateId() {
- return counter.getAndIncrement() + "-" + System.currentTimeMillis();
- }
-}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelProviderUtils.java b/model/api/src/main/java/org/keycloak/models/utils/ModelProviderUtils.java
new file mode 100644
index 0000000000..e4f64cca8e
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelProviderUtils.java
@@ -0,0 +1,50 @@
+package org.keycloak.models.utils;
+
+import java.util.ServiceLoader;
+
+import org.keycloak.models.ModelProvider;
+
+/**
+ * @author Marek Posolda
+ */
+public class ModelProviderUtils {
+
+ public static final String MODEL_PROVIDER = "keycloak.model";
+ public static final String DEFAULT_MODEL_PROVIDER = "jpa";
+
+ public static Iterable getRegisteredProviders() {
+ return ServiceLoader.load(ModelProvider.class);
+ }
+
+ public static ModelProvider getConfiguredModelProvider(Iterable providers) {
+ String configuredProvider = System.getProperty(MODEL_PROVIDER);
+ ModelProvider provider = null;
+
+ if (configuredProvider != null) {
+ for (ModelProvider p : providers) {
+ if (p.getId().equals(configuredProvider)) {
+ provider = p;
+ }
+ }
+ } else {
+ for (ModelProvider p : providers) {
+ if (provider == null) {
+ provider = p;
+ }
+
+ if (p.getId().equals(DEFAULT_MODEL_PROVIDER)) {
+ provider = p;
+ break;
+ }
+ }
+ }
+
+ return provider;
+ }
+
+ public static ModelProvider getConfiguredModelProvider() {
+ return getConfiguredModelProvider(getRegisteredProviders());
+ }
+
+
+}
diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml
index 7ba6691273..ecbf28940f 100755
--- a/model/jpa/pom.xml
+++ b/model/jpa/pom.xml
@@ -59,6 +59,20 @@
+
+
+ org.keycloak
+ keycloak-model-tests
+ ${project.version}
+ tests
+ test
+
+
+ com.h2database
+ h2
+ test
+
+
@@ -70,6 +84,22 @@
1.6
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ default-test
+
+
+ org.keycloak:keycloak-model-tests
+
+
+
+
+
+
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
index 7b7db28237..a072c75cc6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
@@ -2,7 +2,7 @@ package org.keycloak.models.jpa;
import org.keycloak.models.*;
import org.keycloak.models.jpa.entities.*;
-import org.keycloak.models.utils.KeycloakSessionUtils;
+import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
@@ -28,7 +28,7 @@ public class JpaKeycloakSession implements KeycloakSession {
@Override
public RealmModel createRealm(String name) {
- return createRealm(KeycloakSessionUtils.generateId(), name);
+ return createRealm(KeycloakModelUtils.generateId(), name);
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 965cbbee4f..467953ca54 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -13,6 +13,7 @@ import org.keycloak.models.jpa.entities.SocialLinkEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
import org.keycloak.models.jpa.entities.UserScopeMappingEntity;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.PemUtils;
import org.keycloak.models.ApplicationModel;
@@ -187,59 +188,29 @@ public class RealmAdapter implements RealmModel {
@Override
public PublicKey getPublicKey() {
if (publicKey != null) return publicKey;
- String pem = getPublicKeyPem();
- if (pem != null) {
- try {
- publicKey = PemUtils.decodePublicKey(pem);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
+ publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem());
return publicKey;
}
@Override
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
- StringWriter writer = new StringWriter();
- PEMWriter pemWriter = new PEMWriter(writer);
- try {
- pemWriter.writeObject(publicKey);
- pemWriter.flush();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- String s = writer.toString();
- setPublicKeyPem(PemUtils.removeBeginEnd(s));
+ String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
+ setPublicKeyPem(publicKeyPem);
}
@Override
public PrivateKey getPrivateKey() {
if (privateKey != null) return privateKey;
- String pem = getPrivateKeyPem();
- if (pem != null) {
- try {
- privateKey = PemUtils.decodePrivateKey(pem);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
+ privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
return privateKey;
}
@Override
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
- StringWriter writer = new StringWriter();
- PEMWriter pemWriter = new PEMWriter(writer);
- try {
- pemWriter.writeObject(privateKey);
- pemWriter.flush();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- String s = writer.toString();
- setPrivateKeyPem(PemUtils.removeBeginEnd(s));
+ String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey);
+ setPrivateKeyPem(privateKeyPem);
}
protected RequiredCredentialModel initRequiredCredentialModel(String type) {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
index 2c9a8c95a9..e418441de2 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
@@ -6,6 +6,7 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.jpa.entities.ApplicationRoleEntity;
import org.keycloak.models.jpa.entities.RealmRoleEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
+import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
import java.util.HashSet;
@@ -94,27 +95,13 @@ public class RoleAdapter implements RoleModel {
return set;
}
- public static boolean searchFor(RoleModel role, RoleModel composite, Set visited) {
- if (visited.contains(composite)) return false;
- visited.add(composite);
- Set composites = composite.getComposites();
- if (composites.contains(role)) return true;
- for (RoleModel contained : composites) {
- if (!contained.isComposite()) continue;
- if (searchFor(role, contained, visited)) return true;
- }
- return false;
- }
-
-
-
@Override
public boolean hasRole(RoleModel role) {
if (this.equals(role)) return true;
if (!isComposite()) return false;
Set visited = new HashSet();
- return searchFor(role, this, visited);
+ return KeycloakModelUtils.searchFor(role, this, visited);
}
@Override
diff --git a/services/src/test/resources/META-INF/persistence.xml b/model/jpa/src/test/resources/META-INF/persistence.xml
similarity index 100%
rename from services/src/test/resources/META-INF/persistence.xml
rename to model/jpa/src/test/resources/META-INF/persistence.xml
diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml
index 537112a618..fadcc7f268 100755
--- a/model/mongo/pom.xml
+++ b/model/mongo/pom.xml
@@ -5,7 +5,7 @@
keycloak-parent
org.keycloak
- 1.0-alpha-1
+ 1.0-alpha-2-SNAPSHOT
../../pom.xml
4.0.0
@@ -41,27 +41,28 @@
picketlink-common
provided
-
- org.picketlink
- picketlink-idm-api
- provided
-
org.mongodb
mongo-java-driver
provided
+
- de.flapdoodle.embed
- de.flapdoodle.embed.mongo
- test
-
-
- junit
- junit
+ org.keycloak
+ keycloak-model-tests
+ ${project.version}
+ tests
test
+
+
+ localhost
+ 27018
+ keycloak
+ true
+
+
@@ -72,6 +73,66 @@
1.6
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ test
+ integration-test
+
+ test
+
+
+
+ ${keycloak.mongo.host}
+ ${keycloak.mongo.port}
+ ${keycloak.mongo.db}
+ ${keycloak.mongo.clearOnStartup}
+
+
+ org.keycloak:keycloak-model-tests
+
+
+
+
+ default-test
+
+ true
+
+
+
+
+
+
+
+ com.github.joelittlejohn.embedmongo
+ embedmongo-maven-plugin
+
+
+ start-mongodb
+ pre-integration-test
+
+ start
+
+
+ ${keycloak.mongo.port}
+ file
+ ${project.build.directory}/mongodb.log
+
+
+
+ stop-mongodb
+ post-integration-test
+
+ stop
+
+
+
+
+
\ No newline at end of file
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/MongoDBSessionFactoryTestContext.java b/model/mongo/src/main/java/org/keycloak/models/mongo/MongoDBSessionFactoryTestContext.java
deleted file mode 100755
index 1b00fa5b54..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/MongoDBSessionFactoryTestContext.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.keycloak.test.common;
-
-import de.flapdoodle.embed.mongo.MongodExecutable;
-import de.flapdoodle.embed.mongo.MongodProcess;
-import de.flapdoodle.embed.mongo.MongodStarter;
-import de.flapdoodle.embed.mongo.config.MongodConfig;
-import de.flapdoodle.embed.mongo.distribution.Version;
-import de.flapdoodle.embed.process.runtime.Network;
-import org.jboss.resteasy.logging.Logger;
-import org.keycloak.services.utils.PropertiesManager;
-
-/**
- * @author Marek Posolda
- */
-public class MongoDBSessionFactoryTestContext implements SessionFactoryTestContext {
-
- protected static final Logger logger = Logger.getLogger(MongoDBSessionFactoryTestContext.class);
- private static final int PORT = PropertiesManager.MONGO_DEFAULT_PORT_UNIT_TESTS;
-
- private MongodExecutable mongodExe;
- private MongodProcess mongod;
-
- @Override
- public void beforeTestClass() {
- logger.info("Bootstrapping MongoDB on localhost, port " + PORT);
- try {
- mongodExe = MongodStarter.getDefaultInstance().prepare(new MongodConfig(Version.V2_0_5, PORT, Network.localhostIsIPv6()));
- mongod = mongodExe.start();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- logger.info("MongoDB bootstrapped successfully");
- }
-
- @Override
- public void afterTestClass() {
- if (mongodExe != null) {
- if (mongod != null) {
- mongod.stop();
- }
- mongodExe.stop();
- }
- logger.info("MongoDB stopped successfully");
-
- // Reset this, so other tests are not affected
- PropertiesManager.setDefaultSessionFactoryType();
- }
-
- @Override
- public void initEnvironment() {
- PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_MONGO);
- PropertiesManager.setMongoHost("localhost");
- PropertiesManager.setMongoPort(PORT);
- PropertiesManager.setMongoDbName("keycloakTest");
- PropertiesManager.setDropDatabaseOnStartup(true);
- }
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/MongoRunnerListener.java b/model/mongo/src/main/java/org/keycloak/models/mongo/MongoRunnerListener.java
deleted file mode 100755
index a1f67883c8..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/MongoRunnerListener.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.keycloak.services.listeners;
-
-import de.flapdoodle.embed.mongo.MongodExecutable;
-import de.flapdoodle.embed.mongo.MongodProcess;
-import de.flapdoodle.embed.mongo.MongodStarter;
-import de.flapdoodle.embed.mongo.config.MongodConfig;
-import de.flapdoodle.embed.mongo.distribution.Version;
-import de.flapdoodle.embed.process.runtime.Network;
-import org.jboss.resteasy.logging.Logger;
-import org.keycloak.services.utils.PropertiesManager;
-
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-/**
- * @author Marek Posolda
- */
-public class MongoRunnerListener implements ServletContextListener {
-
- protected static final Logger logger = Logger.getLogger(MongoRunnerListener.class);
-
- private MongodExecutable mongodExe;
- private MongodProcess mongod;
-
- @Override
- public void contextInitialized(ServletContextEvent sce) {
- if (PropertiesManager.bootstrapEmbeddedMongoAtContextInit()) {
- int port = PropertiesManager.getMongoPort();
- logger.info("Going to start embedded MongoDB on port=" + port);
-
- try {
- mongodExe = MongodStarter.getDefaultInstance().prepare(new MongodConfig(Version.V2_0_5, port, Network.localhostIsIPv6()));
- mongod = mongodExe.start();
- } catch (Exception e) {
- logger.warn("Couldn't start Embedded Mongo on port " + port + ". Maybe it's already started? Cause: " + e.getClass() + " " + e.getMessage());
- if (logger.isDebugEnabled()) {
- logger.debug("Failed to start MongoDB", e);
- }
- }
- }
- }
-
- @Override
- public void contextDestroyed(ServletContextEvent sce) {
- if (mongodExe != null) {
- if (mongod != null) {
- logger.info("Going to stop embedded MongoDB.");
- mongod.stop();
- }
- mongodExe.stop();
- }
- }
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/PropertiesManager.java b/model/mongo/src/main/java/org/keycloak/models/mongo/PropertiesManager.java
deleted file mode 100755
index 9e897ae238..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/PropertiesManager.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.keycloak.models.mongo;
-
-/**
- * @author Marek Posolda
- */
-public class PropertiesManager {
-
- private static final String MONGO_HOST = "keycloak.mongodb.host";
- private static final String MONGO_PORT = "keycloak.mongodb.port";
- private static final String MONGO_DB_NAME = "keycloak.mongodb.databaseName";
- private static final String MONGO_DROP_DB_ON_STARTUP = "keycloak.mongodb.dropDatabaseOnStartup";
- private static final String BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT = "keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit";
-
- // Port where embedded MongoDB will be started during keycloak bootstrap. Same port will be used by KeycloakApplication then
- private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED = 37017;
-
- // Port where MongoDB instance is normally started on linux. This port should be used if we're not starting embedded instance (keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit is false)
- private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR = 27017;
-
- // Port where unit tests will start embedded MongoDB instance
- public static final int MONGO_DEFAULT_PORT_UNIT_TESTS = 27777;
-
- public static String getMongoHost() {
- return System.getProperty(MONGO_HOST, "localhost");
- }
-
- public static void setMongoHost(String mongoHost) {
- System.setProperty(MONGO_HOST, mongoHost);
- }
-
- public static int getMongoPort() {
- return Integer.parseInt(System.getProperty(MONGO_PORT, String.valueOf(MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED)));
- }
-
- public static void setMongoPort(int mongoPort) {
- System.setProperty(MONGO_PORT, String.valueOf(mongoPort));
- }
-
- public static String getMongoDbName() {
- return System.getProperty(MONGO_DB_NAME, "keycloak");
- }
-
- public static void setMongoDbName(String mongoMongoDbName) {
- System.setProperty(MONGO_DB_NAME, mongoMongoDbName);
- }
-
- public static boolean dropDatabaseOnStartup() {
- return Boolean.parseBoolean(System.getProperty(MONGO_DROP_DB_ON_STARTUP, "true"));
- }
-
- public static void setDropDatabaseOnStartup(boolean dropDatabaseOnStartup) {
- System.setProperty(MONGO_DROP_DB_ON_STARTUP, String.valueOf(dropDatabaseOnStartup));
- }
-
- public static boolean bootstrapEmbeddedMongoAtContextInit() {
- return isMongoSessionFactory() && Boolean.parseBoolean(System.getProperty(BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT, "true"));
- }
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java
deleted file mode 100644
index 81546ba469..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.keycloak.models.mongo.api;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Marek Posolda
- */
-public abstract class AbstractAttributedNoSQLObject extends AbstractNoSQLObject implements AttributedNoSQLObject {
-
- // Simple hashMap for now (no thread-safe)
- private Map attributes = new HashMap();
-
- @Override
- public void setAttribute(String name, String value) {
- attributes.put(name, value);
- }
-
- @Override
- public void removeAttribute(String name) {
- // attributes.remove(name);
-
- // ensure that particular attribute has null value, so it will be deleted in DB. TODO: needs to be improved
- attributes.put(name, null);
- }
-
- @Override
- public String getAttribute(String name) {
- return attributes.get(name);
- }
-
- @Override
- public Map getAttributes() {
- return Collections.unmodifiableMap(attributes);
- }
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java
new file mode 100644
index 0000000000..86e6e2e31f
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java
@@ -0,0 +1,50 @@
+package org.keycloak.models.mongo.api;
+
+import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
+
+/**
+ * @author Marek Posolda
+ */
+public class AbstractMongoIdentifiableEntity implements MongoIdentifiableEntity {
+
+ private String id;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public void afterRemove(MongoStoreInvocationContext invocationContext) {
+ // Empty by default
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+
+ if (this.id == null) return false;
+
+ if (o == null || getClass() != o.getClass()) return false;
+
+ AbstractMongoIdentifiableEntity that = (AbstractMongoIdentifiableEntity) o;
+
+ if (!getId().equals(that.getId())) return false;
+
+ return true;
+
+ }
+
+ @Override
+ public int hashCode() {
+ return id!=null ? id.hashCode() : super.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s [ id=%s ]", getClass().getSimpleName(), getId());
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java
deleted file mode 100644
index 837e5e4644..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.keycloak.models.mongo.api;
-
-/**
- * @author Marek Posolda
- */
-public abstract class AbstractNoSQLObject implements NoSQLObject {
-
- @Override
- public void afterRemove(NoSQL noSQL) {
- // Empty by default
- }
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java
deleted file mode 100644
index 45accd1c1c..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.keycloak.models.mongo.api;
-
-import java.util.Map;
-
-/**
- * @author Marek Posolda
- */
-public interface AttributedNoSQLObject extends NoSQLObject {
-
- void setAttribute(String name, String value);
-
- void removeAttribute(String name);
-
- String getAttribute(String name);
-
- Map getAttributes();
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoCollection.java
similarity index 92%
rename from model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java
rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoCollection.java
index 80b63326f0..8695d12393 100644
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoCollection.java
@@ -15,7 +15,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Retention(RUNTIME)
@Inherited
-public @interface NoSQLCollection {
+public @interface MongoCollection {
String collectionName();
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoEntity.java
new file mode 100644
index 0000000000..8b91583344
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoEntity.java
@@ -0,0 +1,11 @@
+package org.keycloak.models.mongo.api;
+
+import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
+
+/**
+ * Base interface for object, which is persisted in Mongo
+ *
+ * @author Marek Posolda
+ */
+public interface MongoEntity {
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoField.java
similarity index 94%
rename from model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java
rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoField.java
index 3af69a7135..4f19f83e7d 100644
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoField.java
@@ -14,7 +14,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({METHOD, FIELD})
@Documented
@Retention(RUNTIME)
-public @interface NoSQLField {
+public @interface MongoField {
// TODO: fieldName add lazy loading?
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoIdentifiableEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoIdentifiableEntity.java
new file mode 100644
index 0000000000..dfa553ee13
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoIdentifiableEntity.java
@@ -0,0 +1,21 @@
+package org.keycloak.models.mongo.api;
+
+import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
+
+/**
+ * Entity with Id
+ *
+ * @author Marek Posolda
+ */
+public interface MongoIdentifiableEntity extends MongoEntity {
+
+ public String getId();
+
+ public void setId(String id);
+
+ /**
+ * Lifecycle callback, which is called after removal of this object from Mongo.
+ * It may be useful for triggering removal of wired objects.
+ */
+ void afterRemove(MongoStoreInvocationContext invocationContext);
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java
new file mode 100755
index 0000000000..9da25e5b4e
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java
@@ -0,0 +1,43 @@
+package org.keycloak.models.mongo.api;
+
+import com.mongodb.DBObject;
+import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
+
+import java.util.List;
+
+/**
+ * @author Marek Posolda
+ */
+public interface MongoStore {
+
+ /**
+ * Insert new entity
+ *
+ * @param entity to insert
+ */
+ void insertEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context);
+
+ /**
+ * Update existing entity
+ *
+ * @param entity to update
+ */
+ void updateEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context);
+
+
+ T loadEntity(Class type, String id, MongoStoreInvocationContext context);
+
+ T loadSingleEntity(Class type, DBObject query, MongoStoreInvocationContext context);
+
+ List loadEntities(Class type, DBObject query, MongoStoreInvocationContext context);
+
+ boolean removeEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context);
+
+ boolean removeEntity(Class extends MongoIdentifiableEntity> type, String id, MongoStoreInvocationContext context);
+
+ boolean removeEntities(Class extends MongoIdentifiableEntity> type, DBObject query, MongoStoreInvocationContext context);
+
+ boolean pushItemToList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context);
+
+ boolean pullItemFromList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPull, MongoStoreInvocationContext context);
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java
deleted file mode 100755
index 0a63606e2d..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.keycloak.models.mongo.api;
-
-import org.keycloak.models.mongo.api.query.NoSQLQuery;
-import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
-
-import java.util.List;
-
-/**
- * @author Marek Posolda
- */
-public interface NoSQL {
-
- /**
- * Insert object if it's oid is null. Otherwise update
- */
- void saveObject(NoSQLObject object);
-
- T loadObject(Class type, String oid);
-
- T loadSingleObject(Class type, NoSQLQuery query);
-
- List loadObjects(Class type, NoSQLQuery query);
-
- // Object must have filled oid
- void removeObject(NoSQLObject object);
-
- void removeObject(Class extends NoSQLObject> type, String oid);
-
- void removeObjects(Class extends NoSQLObject> type, NoSQLQuery query);
-
- NoSQLQueryBuilder createQueryBuilder();
-
- void pushItemToList(NoSQLObject object, String listPropertyName, S itemToPush);
-
- void pullItemFromList(NoSQLObject object, String listPropertyName, S itemToPull);
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java
deleted file mode 100644
index 06ed01e655..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.keycloak.models.mongo.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 Marek Posolda
- */
-@Target({METHOD, FIELD})
-@Documented
-@Retention(RUNTIME)
-public @interface NoSQLId {
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java
deleted file mode 100644
index 0242243936..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.keycloak.models.mongo.api;
-
-/**
- * Base interface for object, which is persisted in NoSQL database
- *
- * @author Marek Posolda
- */
-public interface NoSQLObject {
-
- /**
- * Lifecycle callback, which is called after removal of this object from NoSQL database.
- * It may be useful for triggering removal of wired objects.
- */
- void afterRemove(NoSQL noSQL);
-
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoStoreInvocationContext.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoStoreInvocationContext.java
new file mode 100644
index 0000000000..358f445d09
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoStoreInvocationContext.java
@@ -0,0 +1,32 @@
+package org.keycloak.models.mongo.api.context;
+
+import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.models.mongo.api.MongoStore;
+
+/**
+ * Context, which provides callback methods to be invoked by MongoStore
+ *
+ * @author Marek Posolda
+ */
+public interface MongoStoreInvocationContext {
+
+ void addCreatedObject(MongoIdentifiableEntity entity);
+
+ void addLoadedObject(MongoIdentifiableEntity entity);
+
+ T getLoadedObject(Class type, String id);
+
+ void addUpdateTask(MongoIdentifiableEntity entityToUpdate, MongoTask task);
+
+ void addRemovedObject(MongoIdentifiableEntity entityToRemove);
+
+ void beforeDBSearch(Class extends MongoIdentifiableEntity> entityType);
+
+ void begin();
+
+ void commit();
+
+ void rollback();
+
+ MongoStore getMongoStore();
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoTask.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoTask.java
new file mode 100644
index 0000000000..0c1d500b69
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoTask.java
@@ -0,0 +1,13 @@
+package org.keycloak.models.mongo.api.context;
+
+import org.keycloak.models.mongo.api.MongoStore;
+
+/**
+ * @author Marek Posolda
+ */
+public interface MongoTask {
+
+ void execute();
+
+ boolean isFullUpdate();
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java
deleted file mode 100644
index 29cc0f31ab..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.keycloak.models.mongo.api.query;
-
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * @author Marek Posolda
- */
-public class NoSQLQuery {
-
- private final Map queryAttributes;
-
- NoSQLQuery(Map queryAttributes) {
- this.queryAttributes = queryAttributes;
- };
-
- public Map getQueryAttributes() {
- return Collections.unmodifiableMap(queryAttributes);
- }
-
- @Override
- public String toString() {
- return "NoSQLQuery [" + queryAttributes + "]";
- }
-
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java
deleted file mode 100644
index dcdb5752a3..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.keycloak.models.mongo.api.query;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * @author Marek Posolda
- */
-public abstract class NoSQLQueryBuilder {
-
- private Map queryAttributes = new HashMap();
-
- protected NoSQLQueryBuilder() {};
-
- public NoSQLQuery build() {
- return new NoSQLQuery(queryAttributes);
- }
-
- public NoSQLQueryBuilder andCondition(String name, Object value) {
- this.put(name, value);
- return this;
- }
-
- public abstract NoSQLQueryBuilder inCondition(String name, List> values);
-
- protected void put(String name, Object value) {
- queryAttributes.put(name, value);
- }
-
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java
deleted file mode 100644
index a6b6c869e6..0000000000
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.keycloak.models.mongo.api.types;
-
-/**
- * SPI object to convert object from application type to database type and vice versa. Shouldn't be directly used by application.
- * Various converters should be registered in TypeConverter, which is main entry point to be used by application
- *
- * @author Marek Posolda
- */
-public interface Converter {
-
- S convertObject(T objectToConvert);
-
- Class extends T> getConverterObjectType();
-
- Class getExpectedReturnType();
-}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Mapper.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Mapper.java
new file mode 100644
index 0000000000..c93acde086
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Mapper.java
@@ -0,0 +1,22 @@
+package org.keycloak.models.mongo.api.types;
+
+/**
+ * SPI object to convert object from application type to database type and vice versa. Shouldn't be directly used by application.
+ * Various mappers should be registered in TypeMapper, which is main entry point to be used by application
+ *
+ * @author Marek Posolda
+ */
+public interface Mapper {
+
+ /**
+ * Convert object from one type to expected type
+ *
+ * @param mapperContext Encapsulates reference to converted object and other things, which might be helpful in conversion
+ * @return converted object
+ */
+ S convertObject(MapperContext mapperContext);
+
+ Class extends T> getTypeOfObjectToConvert();
+
+ Class getExpectedReturnType();
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/MapperContext.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/MapperContext.java
new file mode 100644
index 0000000000..987f18b0a4
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/MapperContext.java
@@ -0,0 +1,36 @@
+package org.keycloak.models.mongo.api.types;
+
+import java.util.List;
+
+/**
+ * @author Marek Posolda
+ */
+public class MapperContext {
+
+ // object to convert
+ private final T objectToConvert;
+
+ // expected return type, which could be useful information in some converters, so they are able to dynamically instantiate types
+ private final Class extends S> expectedReturnType;
+
+ // in case that expected return type is generic type (like "List"), then genericTypes could contain list of expected generic arguments
+ private final List> genericTypes;
+
+ public MapperContext(T objectToConvert, Class extends S> expectedReturnType, List> genericTypes) {
+ this.objectToConvert = objectToConvert;
+ this.expectedReturnType = expectedReturnType;
+ this.genericTypes = genericTypes;
+ }
+
+ public T getObjectToConvert() {
+ return objectToConvert;
+ }
+
+ public Class extends S> getExpectedReturnType() {
+ return expectedReturnType;
+ }
+
+ public List> getGenericTypes() {
+ return genericTypes;
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/MapperRegistry.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/MapperRegistry.java
new file mode 100755
index 0000000000..2c1c270c9f
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/MapperRegistry.java
@@ -0,0 +1,111 @@
+package org.keycloak.models.mongo.api.types;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Registry of mappers, which allow to convert application object to database objects. MapperRegistry is main entry point to be used by application.
+ * Application can create instance of MapperRegistry and then register required Mapper objects.
+ *
+ * @author Marek Posolda
+ */
+public class MapperRegistry {
+
+ // TODO: Thread-safety support (maybe...)
+ // Mappers of Application objects to DB objects
+ private Map, Mapper, ?>> appObjectMappers = new HashMap, Mapper, ?>>();
+
+ // Mappers of DB objects to Application objects
+ private Map, Map, Mapper, ?>>> dbObjectMappers = new HashMap, Map, Mapper,?>>>();
+
+
+ /**
+ * Add mapper for converting application objects to DB objects
+ *
+ * @param mapper
+ */
+ public void addAppObjectMapper(Mapper, ?> mapper) {
+ appObjectMappers.put(mapper.getTypeOfObjectToConvert(), mapper);
+ }
+
+
+ /**
+ * Add mapper for converting DB objects to application objects
+ *
+ * @param mapper
+ */
+ public void addDBObjectMapper(Mapper, ?> mapper) {
+ Class> dbObjectType = mapper.getTypeOfObjectToConvert();
+ Class> appObjectType = mapper.getExpectedReturnType();
+ Map, Mapper, ?>> appObjects = dbObjectMappers.get(dbObjectType);
+ if (appObjects == null) {
+ appObjects = new HashMap, Mapper, ?>>();
+ dbObjectMappers.put(dbObjectType, appObjects);
+ }
+ appObjects.put(appObjectType, mapper);
+ }
+
+
+ public S convertDBObjectToApplicationObject(MapperContext