keycloak-uncached
Changes
export-import/export-import-api/pom.xml 38(+38 -0)
export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportProvider.java 11(+11 -0)
export-import/export-import-impl/pom.xml 228(+228 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportPropertiesManager.java 152(+152 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportProviderImpl.java 84(+84 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportImportIOProvider.java 36(+36 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportWriter.java 82(+82 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirImportReader.java 67(+67 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ExportImportIOProvider.java 14(+14 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ExportWriter.java 13(+13 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ImportReader.java 13(+13 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPExportWriter.java 66(+66 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPImportReader.java 70(+70 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPIOProvider.java 48(+48 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelExporter.java 334(+334 -0)
export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelImporter.java 329(+329 -0)
export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportImportProvider 1(+1 -0)
export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.io.ExportImportIOProvider 2(+2 -0)
export-import/export-import-impl/src/test/java/org/keycloak/exportimport/ExportImportTestBase.java 110(+110 -0)
export-import/export-import-impl/src/test/java/org/keycloak/exportimport/JPAToMongoExportImportTest.java 37(+37 -0)
export-import/export-import-impl/src/test/java/org/keycloak/exportimport/MongoToJPAExportImportTest.java 70(+70 -0)
export-import/pom.xml 22(+22 -0)
model/api/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java 23(+23 -0)
model/api/src/main/java/org/keycloak/models/utils/reflection/SetAccessiblePrivilegedAction.java 22(+22 -0)
model/jpa/pom.xml 15(+15 -0)
model/mongo/pom.xml 5(+0 -5)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractMongoAdapter.java 4(+2 -2)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java 32(+19 -13)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java 46(+26 -20)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java 19(+12 -7)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java 35(+18 -17)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java 6(+3 -3)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java 238(+163 -75)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UsernameLoginFailureAdapter.java 16(+10 -6)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/config/SystemPropertiesMongoClientProvider.java 10(+5 -5)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java 26(+26 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java 10(+7 -3)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java 36(+36 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java 85(+18 -67)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java 32(+32 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUsernameLoginFailureEntity.java 18(+18 -0)
pom.xml 8(+8 -0)
server/pom.xml 15(+15 -0)
services/pom.xml 6(+6 -0)
testsuite/integration/pom.xml 17(+17 -0)
Details
export-import/export-import-api/pom.xml 38(+38 -0)
diff --git a/export-import/export-import-api/pom.xml b/export-import/export-import-api/pom.xml
new file mode 100644
index 0000000..7f94100
--- /dev/null
+++ b/export-import/export-import-api/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-export-import</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-1-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-export-import-api</artifactId>
+ <name>Keycloak Export Import API</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportProvider.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportProvider.java
new file mode 100644
index 0000000..9ada46b
--- /dev/null
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportProvider.java
@@ -0,0 +1,11 @@
+package org.keycloak.exportimport;
+
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface ExportImportProvider {
+
+ void checkExportImport(KeycloakSessionFactory identitySessionFactory);
+}
export-import/export-import-impl/pom.xml 228(+228 -0)
diff --git a/export-import/export-import-impl/pom.xml b/export-import/export-import-impl/pom.xml
new file mode 100644
index 0000000..a4898e5
--- /dev/null
+++ b/export-import/export-import-impl/pom.xml
@@ -0,0 +1,228 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-export-import</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-1-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-export-import-impl</artifactId>
+ <name>Keycloak Export Import Impl</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-export-import-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-audit-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>jaxrs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-jaxrs</artifactId>
+ <scope>provided</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-core-asl</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-mapper-asl</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.iharder</groupId>
+ <artifactId>base64</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk16</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Encrypted ZIP -->
+ <dependency>
+ <groupId>de.idyl</groupId>
+ <artifactId>winzipaes</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-tests</artifactId>
+ <version>${project.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-jpa</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-jpa</artifactId>
+ <version>${project.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-mongo</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate.javax.persistence</groupId>
+ <artifactId>hibernate-jpa-2.0-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-entitymanager</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mongodb</groupId>
+ <artifactId>mongo-java-driver</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <properties>
+ <keycloak.mongo.host>localhost</keycloak.mongo.host>
+ <keycloak.mongo.port>27018</keycloak.mongo.port>
+ <keycloak.mongo.db>keycloak</keycloak.mongo.db>
+ <keycloak.mongo.clearOnStartup>true</keycloak.mongo.clearOnStartup>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+
+ <!-- Postpone tests to "integration-test" phase, so that we can bootstrap embedded mongo on 27018 before running tests -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>test</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <systemPropertyVariables>
+ <keycloak.mongo.host>${keycloak.mongo.host}</keycloak.mongo.host>
+ <keycloak.mongo.port>${keycloak.mongo.port}</keycloak.mongo.port>
+ <keycloak.mongo.db>${keycloak.mongo.db}</keycloak.mongo.db>
+ <keycloak.mongo.clearOnStartup>${keycloak.mongo.clearOnStartup}</keycloak.mongo.clearOnStartup>
+ </systemPropertyVariables>
+ </configuration>
+ </execution>
+ <execution>
+ <id>default-test</id>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- Embedded mongo -->
+ <plugin>
+ <groupId>com.github.joelittlejohn.embedmongo</groupId>
+ <artifactId>embedmongo-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>start-mongodb</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>start</goal>
+ </goals>
+ <configuration>
+ <port>${keycloak.mongo.port}</port>
+ <logging>file</logging>
+ <logFile>${project.build.directory}/mongodb.log</logFile>
+ </configuration>
+ </execution>
+ <execution>
+ <id>stop-mongodb</id>
+ <phase>post-integration-test</phase>
+ <goals>
+ <goal>stop</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+
+</project>
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportPropertiesManager.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportPropertiesManager.java
new file mode 100644
index 0000000..87dfa55
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportPropertiesManager.java
@@ -0,0 +1,152 @@
+package org.keycloak.exportimport;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.utils.reflection.Property;
+import org.keycloak.models.utils.reflection.PropertyCriteria;
+import org.keycloak.models.utils.reflection.PropertyQueries;
+
+/**
+ * Not thread safe (objectProperties). Assumption is that export/import is executed once per JVM
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ExportImportPropertiesManager {
+
+ private static final Logger logger = Logger.getLogger(ExportImportPropertiesManager.class);
+
+ private Map<Class<?>, Map<String, Property<Object>>> objectProperties = new HashMap<Class<?>, Map<String, Property<Object>>>();
+
+ // Add properties of class to objectProperties
+ protected void initClassProperties(Class<?> clazz) {
+ Map<String, Property<Object>> classProps = PropertyQueries.createQuery(clazz).addCriteria(new NonEmptyGetterCriteria()).getWritableResultList();
+ this.objectProperties.put(clazz, classProps);
+ }
+
+ public void setBasicPropertiesFromModel(Object model, Object entity) {
+ Class<?> modelClass = getModelClass(model);
+
+ // Lazy init of properties
+ checkPropertiesAvailable(modelClass, entity.getClass());
+
+ Map<String, Property<Object>> modelProps = this.objectProperties.get(modelClass);
+ Map<String, Property<Object>> entityProps = this.objectProperties.get(entity.getClass());
+
+ Map<String, Property<Object>> entityPropsCopy = new HashMap<String, Property<Object>>(entityProps);
+
+ logger.debugf("Properties of entity %s: %s", entity, entityProps.keySet());
+ for (Property<Object> modelProperty : modelProps.values()) {
+ Property<Object> entityProperty = entityPropsCopy.get(modelProperty.getName());
+
+ entityPropsCopy.remove(modelProperty.getName());
+
+ if (entityProperty != null) {
+ Object propertyValue = modelProperty.getValue(model);
+
+ // Workaround needed because model classes have many getters/setters with "Set", but for entity classes, there are usually "List"
+ if (propertyValue instanceof Set) {
+ Set propValueAsSet = (Set)propertyValue;
+ entityProperty.setValue(entity, new ArrayList(propValueAsSet));
+ } else {
+ entityProperty.setValue(entity, propertyValue);
+ }
+ if (logger.isTraceEnabled()) {
+ logger.tracef("Property %s successfully set in JSON to entity %s", modelProperty.getName(), entity);
+ }
+ } else {
+ logger.debugf("Property %s not known in JSON for entity %s", modelProperty.getName(), entity);
+ }
+ }
+
+ logger.debugf("Entity properties for manual setup: %s", entityPropsCopy.keySet());
+ }
+
+ private void checkPropertiesAvailable(Class<?> modelClass, Class<?> entityClass) {
+ if (!objectProperties.containsKey(modelClass)) {
+ initClassProperties(modelClass);
+ }
+ if (!objectProperties.containsKey(entityClass)) {
+ initClassProperties(entityClass);
+ }
+ }
+
+ public void setBasicPropertiesToModel(Object model, Object entity) {
+ Class<?> modelClass = getModelClass(model);
+
+ // Lazy init of properties
+ checkPropertiesAvailable(modelClass, entity.getClass());
+
+ Map<String, Property<Object>> modelProps = this.objectProperties.get(modelClass);
+ Map<String, Property<Object>> entityProps = this.objectProperties.get(entity.getClass());
+
+ Map<String, Property<Object>> entityPropsCopy = new HashMap<String, Property<Object>>(entityProps);
+
+ logger.debugf("Properties of exported entity %s: %s", entity, entityProps.keySet());
+
+ for (Property<Object> modelProperty : modelProps.values()) {
+ Property<Object> entityProperty = entityPropsCopy.get(modelProperty.getName());
+
+ entityPropsCopy.remove(modelProperty.getName());
+
+ if (entityProperty != null) {
+ Object propertyValue = entityProperty.getValue(entity);
+
+ // Workaround needed because model classes have many getters/setters with "Set", but for entity classes, there are usually "List"
+ if (propertyValue instanceof List && Set.class.isAssignableFrom(modelProperty.getJavaClass())) {
+ List propValueAsList = (List)propertyValue;
+ modelProperty.setValue(model, new HashSet(propValueAsList));
+ } else {
+ modelProperty.setValue(model, propertyValue);
+ }
+
+ if (logger.isTraceEnabled()) {
+ logger.tracef("Property %s successfully set in model from entity %s", modelProperty.getName(), entity);
+ }
+ } else {
+ logger.debugf("Property %s not known for entity %s", modelProperty.getName(), entity);
+ }
+ }
+
+ logger.debugf("Entity properties for manual setup: %s", entityPropsCopy.keySet());
+ }
+
+ protected Class<?> getModelClass(Object model) {
+ Class<?> modelClass = model.getClass();
+ Class<?>[] interfaces = modelClass.getInterfaces();
+
+ // Bit unsafe, but looks that it works for all "model adapters" so far
+ if (interfaces.length == 0) {
+ return modelClass;
+ } else {
+ return interfaces[0];
+ }
+ }
+
+ public static class NonEmptyGetterCriteria implements PropertyCriteria {
+
+ private static final List<String> IGNORED_METHODS = Arrays.asList("getPasswordPolicy", "getAuthenticationProviders");
+
+ @Override
+ public boolean methodMatches(Method m) {
+ // Ignore non-empty getters
+ if (m.getParameterTypes().length > 0) {
+ return false;
+ }
+
+ // Ignore some "known" getters (for example incompatible types between model and entity)
+ if (IGNORED_METHODS.contains(m.getName())) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportProviderImpl.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportProviderImpl.java
new file mode 100644
index 0000000..32e0b84
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportProviderImpl.java
@@ -0,0 +1,84 @@
+package org.keycloak.exportimport;
+
+import org.jboss.logging.Logger;
+import org.keycloak.exportimport.io.ExportImportIOProvider;
+import org.keycloak.exportimport.io.ExportWriter;
+import org.keycloak.exportimport.io.ImportReader;
+import org.keycloak.models.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.util.ProviderLoader;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ExportImportProviderImpl implements ExportImportProvider {
+
+ private static final Logger logger = Logger.getLogger(ExportImportProviderImpl.class);
+
+ public static final String ACTION_EXPORT = "export";
+ public static final String ACTION_IMPORT = "import";
+
+ @Override
+ public void checkExportImport(KeycloakSessionFactory identitySessionFactory) {
+ String exportImportAction = Config.getExportImportAction();
+
+ boolean export = false;
+ boolean importt = false;
+ if (ACTION_EXPORT.equals(exportImportAction)) {
+ logger.infof("Full model export requested");
+ export = true;
+ } else if (ACTION_IMPORT.equals(exportImportAction)) {
+ logger.infof("Full model import requested");
+ importt = true;
+ }
+
+ if (export || importt) {
+ KeycloakSession session = identitySessionFactory.createSession();
+ KeycloakTransaction transaction = session.getTransaction();
+ try {
+ transaction.begin();
+
+ if (export) {
+ ExportWriter exportWriter = getProvider().getExportWriter();
+ new ModelExporter().exportModel(session, exportWriter);
+ logger.infof("Export finished successfully");
+ } else {
+ ImportReader importReader = getProvider().getImportReader();
+ new ModelImporter().importModel(session, importReader);
+ logger.infof("Import finished successfully");
+ }
+
+ if (transaction.isActive()) {
+ if (transaction.getRollbackOnly()) {
+ transaction.rollback();
+ } else {
+ transaction.commit();
+ }
+ }
+ } catch (Exception e) {
+ if (transaction.isActive()) {
+ session.getTransaction().rollback();
+ }
+ throw new RuntimeException(e);
+ } finally {
+ session.close();
+ }
+ }
+ }
+
+ private ExportImportIOProvider getProvider() {
+ String providerId = Config.getExportImportProvider();
+ logger.infof("Requested migration provider: " + providerId);
+
+ Iterable<ExportImportIOProvider> providers = ProviderLoader.load(ExportImportIOProvider.class);
+ for (ExportImportIOProvider provider : providers) {
+ if (providerId.equals(provider.getId())) {
+ return provider;
+ }
+ }
+
+ throw new IllegalStateException("Provider " + providerId + " not found");
+ }
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportImportIOProvider.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportImportIOProvider.java
new file mode 100644
index 0000000..962c3ac
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportImportIOProvider.java
@@ -0,0 +1,36 @@
+package org.keycloak.exportimport.io.directory;
+
+import java.io.File;
+
+import org.keycloak.exportimport.io.ExportImportIOProvider;
+import org.keycloak.exportimport.io.ExportWriter;
+import org.keycloak.exportimport.io.ImportReader;
+import org.keycloak.models.Config;
+
+/**
+ * Export/import into JSON files inside "tmp" directory. This implementation is used mainly for testing
+ * (shouldn't be used in production due to passwords in JSON files)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TmpDirExportImportIOProvider implements ExportImportIOProvider {
+
+ public static final String PROVIDER_ID = "dir";
+
+ @Override
+ public ExportWriter getExportWriter() {
+ String dir = Config.getExportImportDir();
+ return dir!=null ? new TmpDirExportWriter(new File(dir)) : new TmpDirExportWriter();
+ }
+
+ @Override
+ public ImportReader getImportReader() {
+ String dir = Config.getExportImportDir();
+ return dir!=null ? new TmpDirImportReader(new File(dir)) : new TmpDirImportReader();
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportWriter.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportWriter.java
new file mode 100644
index 0000000..d1ee454
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportWriter.java
@@ -0,0 +1,82 @@
+package org.keycloak.exportimport.io.directory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.jboss.logging.Logger;
+import org.keycloak.exportimport.io.ExportWriter;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TmpDirExportWriter implements ExportWriter {
+
+ private static final Logger logger = Logger.getLogger(TmpDirExportWriter.class);
+
+ private final ObjectMapper objectMapper;
+ private final File rootDirectory;
+
+ public TmpDirExportWriter() {
+ // Determine system tmp directory
+ String tempDir = System.getProperty("java.io.tmpdir");
+
+ // Delete and recreate directory inside tmp
+ this.rootDirectory = new File(tempDir + "/keycloak-export");
+ if (this.rootDirectory .exists()) {
+ recursiveDeleteDir(this.rootDirectory );
+ }
+ this.rootDirectory.mkdirs();
+
+ logger.infof("Exporting into directory %s", this.rootDirectory.getAbsolutePath());
+ this.objectMapper = getObjectMapper();
+ }
+
+ public TmpDirExportWriter(File rootDirectory) {
+ this.rootDirectory = rootDirectory;
+ this.rootDirectory.mkdirs();
+ this.objectMapper = getObjectMapper();
+
+ logger.infof("Exporting into directory %s", this.rootDirectory.getAbsolutePath());
+ }
+
+ private ObjectMapper getObjectMapper() {
+ return JsonSerialization.prettyMapper;
+ }
+
+ protected boolean recursiveDeleteDir(File dirPath) {
+ if (dirPath.exists()) {
+ File[] files = dirPath.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ if (files[i].isDirectory()) {
+ recursiveDeleteDir(files[i]);
+ } else {
+ files[i].delete();
+ }
+ }
+ }
+ if (dirPath.exists())
+ return dirPath.delete();
+ else
+ return true;
+ }
+
+ @Override
+ public <T> void writeEntities(String fileName, List<T> entities) {
+ try {
+ File file = new File(this.rootDirectory, fileName);
+ FileOutputStream stream = new FileOutputStream(file);
+ this.objectMapper.writeValue(stream, entities);
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ @Override
+ public void closeExportWriter() {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirImportReader.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirImportReader.java
new file mode 100644
index 0000000..942e23a
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirImportReader.java
@@ -0,0 +1,67 @@
+package org.keycloak.exportimport.io.directory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.List;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.jboss.logging.Logger;
+import org.keycloak.exportimport.io.ImportReader;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TmpDirImportReader implements ImportReader {
+
+ private static final Logger logger = Logger.getLogger(TmpDirImportReader.class);
+
+ private final ObjectMapper objectMapper;
+ private final File rootDirectory;
+
+ public TmpDirImportReader() {
+ // Determine system tmp directory
+ String tempDir = System.getProperty("java.io.tmpdir");
+
+ // Delete and recreate directory inside tmp
+ this.rootDirectory = new File(tempDir + "/keycloak-export");
+ if (!this.rootDirectory .exists()) {
+ throw new IllegalStateException("Directory " + this.rootDirectory + " doesn't exists");
+ }
+
+ logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath());
+ this.objectMapper = getObjectMapper();
+ }
+
+ public TmpDirImportReader(File rootDirectory) {
+ this.rootDirectory = rootDirectory;
+ this.objectMapper = getObjectMapper();
+
+ logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath());
+ }
+
+ private ObjectMapper getObjectMapper() {
+ return JsonSerialization.prettyMapper;
+ }
+
+ @Override
+ public <T> List<T> readEntities(String fileName, Class<T> entityClass) {
+ try {
+ File file = new File(this.rootDirectory, fileName);
+ FileInputStream stream = new FileInputStream(file);
+ T[] template = (T[]) Array.newInstance(entityClass, 0);
+ T[] result = (T[])this.objectMapper.readValue(stream, template.getClass());
+ return Arrays.asList(result);
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ @Override
+ public void closeImportReader() {
+ //TODO: Should directory be deleted after import?
+ }
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ExportImportIOProvider.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ExportImportIOProvider.java
new file mode 100644
index 0000000..e53b331
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ExportImportIOProvider.java
@@ -0,0 +1,14 @@
+package org.keycloak.exportimport.io;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface ExportImportIOProvider {
+
+ String getId();
+
+ ExportWriter getExportWriter();
+
+ ImportReader getImportReader();
+
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ExportWriter.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ExportWriter.java
new file mode 100644
index 0000000..c90a57f
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ExportWriter.java
@@ -0,0 +1,13 @@
+package org.keycloak.exportimport.io;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface ExportWriter {
+
+ <T> void writeEntities(String fileName, List<T> entities);
+
+ void closeExportWriter();
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ImportReader.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ImportReader.java
new file mode 100644
index 0000000..83faabd
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ImportReader.java
@@ -0,0 +1,13 @@
+package org.keycloak.exportimport.io;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface ImportReader {
+
+ <T> List<T> readEntities(String fileName, Class<T> entityClass);
+
+ void closeImportReader();
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPExportWriter.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPExportWriter.java
new file mode 100644
index 0000000..0b49940
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPExportWriter.java
@@ -0,0 +1,66 @@
+package org.keycloak.exportimport.io.zip;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import de.idyl.winzipaes.AesZipFileEncrypter;
+import de.idyl.winzipaes.impl.AESEncrypter;
+import de.idyl.winzipaes.impl.AESEncrypterBC;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.keycloak.exportimport.io.ExportWriter;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class EncryptedZIPExportWriter implements ExportWriter {
+
+ private final File zipFile;
+ private final ObjectMapper objectMapper;
+ private final String password;
+
+ private final AesZipFileEncrypter encrypter;
+
+
+ public EncryptedZIPExportWriter(String zipFileName, String password) {
+ try {
+ this.zipFile = new File(zipFileName);
+ if (zipFile.exists()) {
+ throw new IllegalStateException("File " + zipFileName + " already exists");
+ }
+
+ this.objectMapper = JsonSerialization.mapper;
+
+ AESEncrypter encrypter = new AESEncrypterBC();
+ this.encrypter = new AesZipFileEncrypter(this.zipFile, encrypter);
+ this.password = password;
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ @Override
+ public <T> void writeEntities(String fileName, List<T> entities) {
+ try {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ this.objectMapper.writeValue(stream, entities);
+
+ byte[] byteArray = stream.toByteArray();
+ this.encrypter.add(fileName, new ByteArrayInputStream(byteArray), this.password);
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ @Override
+ public void closeExportWriter() {
+ try {
+ this.encrypter.close();
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPImportReader.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPImportReader.java
new file mode 100644
index 0000000..d115b7d
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPImportReader.java
@@ -0,0 +1,70 @@
+package org.keycloak.exportimport.io.zip;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.List;
+
+import de.idyl.winzipaes.AesZipFileDecrypter;
+import de.idyl.winzipaes.impl.AESDecrypter;
+import de.idyl.winzipaes.impl.AESDecrypterBC;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.keycloak.exportimport.io.ImportReader;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class EncryptedZIPImportReader implements ImportReader {
+
+ private final File zipFile;
+ private final ObjectMapper objectMapper;
+ private final String password;
+
+ private final AesZipFileDecrypter decrypter;
+
+
+ public EncryptedZIPImportReader(String zipFileName, String password) {
+ try {
+ this.zipFile = new File(zipFileName);
+ if (!zipFile.exists()) {
+ throw new IllegalStateException("File " + zipFileName + " doesn't exists");
+ }
+
+ this.objectMapper = JsonSerialization.mapper;
+
+ AESDecrypter decrypter = new AESDecrypterBC();
+ this.decrypter = new AesZipFileDecrypter(this.zipFile, decrypter);
+ this.password = password;
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ @Override
+ public <T> List<T> readEntities(String fileName, Class<T> entityClass) {
+ try {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ this.decrypter.extractEntry(this.decrypter.getEntry(fileName), bos, this.password);
+
+ ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+ T[] template = (T[]) Array.newInstance(entityClass, 0);
+ T[] result = (T[])this.objectMapper.readValue(bis, template.getClass());
+ return Arrays.asList(result);
+ } catch (Exception ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ @Override
+ public void closeImportReader() {
+ try {
+ this.decrypter.close();
+ } catch (Exception ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPIOProvider.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPIOProvider.java
new file mode 100644
index 0000000..837157e
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPIOProvider.java
@@ -0,0 +1,48 @@
+package org.keycloak.exportimport.io.zip;
+
+import org.jboss.logging.Logger;
+import org.keycloak.exportimport.io.ExportImportIOProvider;
+import org.keycloak.exportimport.io.ExportWriter;
+import org.keycloak.exportimport.io.ImportReader;
+import org.keycloak.models.Config;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class EncryptedZIPIOProvider implements ExportImportIOProvider {
+
+ private static final Logger logger = Logger.getLogger(EncryptedZIPIOProvider.class);
+
+ public static final String PROVIDER_ID = "zip";
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public ExportWriter getExportWriter() {
+ String zipFile = Config.getExportImportZipFile();
+ String zipPassword = Config.getExportImportZipPassword();
+ logger.infof("Using zip for export: " + zipFile);
+
+ if (zipFile==null || zipPassword==null) {
+ throw new IllegalArgumentException("zipFile or zipPassword are null");
+ }
+
+ return new EncryptedZIPExportWriter(zipFile, zipPassword);
+ }
+
+ @Override
+ public ImportReader getImportReader() {
+ String zipFile = Config.getExportImportZipFile();
+ String zipPassword = Config.getExportImportZipPassword();
+ logger.infof("Using zip for import: " + zipFile);
+
+ if (zipFile==null || zipPassword==null) {
+ throw new IllegalArgumentException("zipFile or zipPassword are null");
+ }
+
+ return new EncryptedZIPImportReader(zipFile, zipPassword);
+ }
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelExporter.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelExporter.java
new file mode 100644
index 0000000..b1893f3
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelExporter.java
@@ -0,0 +1,334 @@
+package org.keycloak.exportimport;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.jboss.logging.Logger;
+import org.keycloak.exportimport.io.ExportWriter;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.SocialLinkModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.entities.ApplicationEntity;
+import org.keycloak.models.entities.AuthenticationLinkEntity;
+import org.keycloak.models.entities.AuthenticationProviderEntity;
+import org.keycloak.models.entities.CredentialEntity;
+import org.keycloak.models.entities.OAuthClientEntity;
+import org.keycloak.models.entities.RealmEntity;
+import org.keycloak.models.entities.RequiredCredentialEntity;
+import org.keycloak.models.entities.RoleEntity;
+import org.keycloak.models.entities.SocialLinkEntity;
+import org.keycloak.models.entities.UserEntity;
+import org.keycloak.models.entities.UsernameLoginFailureEntity;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ModelExporter {
+
+ private static final Logger logger = Logger.getLogger(ModelExporter.class);
+
+ private ExportWriter exportWriter;
+ private ExportImportPropertiesManager propertiesManager;
+
+ public void exportModel(KeycloakSession keycloakSession, ExportWriter exportWriter) {
+ // Initialize needed objects
+ this.exportWriter = exportWriter;
+ this.propertiesManager = new ExportImportPropertiesManager();
+
+ // Create separate files for "realms", "applications", "oauthClients", "roles" and finally "users". Users may be done in more files (pagination)
+ exportRealms(keycloakSession, "realms.json");
+ exportApplications(keycloakSession, "applications.json");
+ exportOAuthClients(keycloakSession, "oauthClients.json");
+ exportRoles(keycloakSession, "roles.json");
+ exportUsers(keycloakSession, "users.json");
+ exportUserFailures(keycloakSession, "userFailures.json");
+
+ this.exportWriter.closeExportWriter();
+ }
+
+ protected void exportRealms(KeycloakSession keycloakSession, String fileName) {
+ List<RealmModel> realms = keycloakSession.getRealms();
+
+ // Convert models to entities, which will be written into JSON file
+ List<RealmEntity> result = new LinkedList<RealmEntity>();
+ for (RealmModel realmModel : realms) {
+ RealmEntity entity = new RealmEntity();
+ entity.setId(realmModel.getId());
+ result.add(entity);
+
+ // Export all basic properties from realm
+ this.propertiesManager.setBasicPropertiesFromModel(realmModel, entity);
+
+ // Export 'advanced' properties
+ ApplicationModel realmAdminApp = realmModel.getAdminApp();
+ if (realmAdminApp != null) {
+ entity.setAdminAppId(realmAdminApp.getId());
+ }
+ entity.setDefaultRoles(realmModel.getDefaultRoles());
+
+ List<RequiredCredentialEntity> reqCredEntities = new ArrayList<RequiredCredentialEntity>();
+ List<RequiredCredentialModel> requiredCredModels = realmModel.getRequiredCredentials();
+ for (RequiredCredentialModel requiredCredModel : requiredCredModels) {
+ RequiredCredentialEntity reqCredEntity = new RequiredCredentialEntity();
+ this.propertiesManager.setBasicPropertiesFromModel(requiredCredModel, reqCredEntity);
+ reqCredEntities.add(reqCredEntity);
+ }
+ entity.setRequiredCredentials(reqCredEntities);
+
+ // password policy
+ entity.setPasswordPolicy(realmModel.getPasswordPolicy().toString());
+
+ // authentication providers
+ List<AuthenticationProviderEntity> authProviderEntities = new ArrayList<AuthenticationProviderEntity>();
+ for (AuthenticationProviderModel authProvider : realmModel.getAuthenticationProviders()) {
+ AuthenticationProviderEntity authProviderEntity = new AuthenticationProviderEntity();
+ this.propertiesManager.setBasicPropertiesFromModel(authProvider, authProviderEntity);
+ authProviderEntities.add(authProviderEntity);
+
+ }
+ entity.setAuthenticationProviders(authProviderEntities);
+ }
+
+ this.exportWriter.writeEntities(fileName, result);
+ logger.infof("Realms exported: " + result);
+ }
+
+ protected void exportApplications(KeycloakSession keycloakSession, String fileName) {
+ List<ApplicationModel> allApplications = getAllApplications(keycloakSession);
+
+ List<ApplicationEntity> result = new LinkedList<ApplicationEntity>();
+ for (ApplicationModel appModel : allApplications) {
+ ApplicationEntity appEntity = new ApplicationEntity();
+ appEntity.setId(appModel.getId());
+ result.add(appEntity);
+
+ this.propertiesManager.setBasicPropertiesFromModel(appModel, appEntity);
+
+ // Export 'advanced' properties of application
+ appEntity.setRealmId(appModel.getRealm().getId());
+ appEntity.setDefaultRoles(appModel.getDefaultRoles());
+
+ List<String> scopeIds = getScopeIds(appModel);
+ appEntity.setScopeIds(scopeIds);
+ }
+
+ this.exportWriter.writeEntities(fileName, result);
+ logger.infof("Applications exported: " + result);
+ }
+
+ protected void exportOAuthClients(KeycloakSession keycloakSession, String fileName) {
+ List<RealmModel> realms = keycloakSession.getRealms();
+ List<OAuthClientModel> allClients = new ArrayList<OAuthClientModel>();
+ for (RealmModel realmModel : realms) {
+ allClients.addAll(realmModel.getOAuthClients());
+ }
+
+ List<OAuthClientEntity> result = new LinkedList<OAuthClientEntity>();
+ for (OAuthClientModel clientModel : allClients) {
+ OAuthClientEntity clientEntity = new OAuthClientEntity();
+ clientEntity.setId(clientModel.getId());
+ result.add(clientEntity);
+
+ this.propertiesManager.setBasicPropertiesFromModel(clientModel, clientEntity);
+
+ // Export 'advanced' properties of client
+ clientEntity.setName(clientModel.getClientId());
+ clientEntity.setRealmId(clientModel.getRealm().getId());
+
+ List<String> scopeIds = getScopeIds(clientModel);
+ clientEntity.setScopeIds(scopeIds);
+ }
+
+ this.exportWriter.writeEntities(fileName, result);
+ logger.infof("OAuth clients exported: " + result);
+ }
+
+ protected void exportRoles(KeycloakSession keycloakSession, String fileName) {
+ List<RoleModel> allRoles = getAllRoles(keycloakSession);
+
+ List<RoleEntity> result = new LinkedList<RoleEntity>();
+ for (RoleModel roleModel : allRoles) {
+ RoleEntity roleEntity = new RoleEntity();
+ roleEntity.setId(roleModel.getId());
+ result.add(roleEntity);
+
+ roleEntity.setName(roleModel.getName());
+ roleEntity.setDescription(roleModel.getDescription());
+
+ RoleContainerModel roleContainer = roleModel.getContainer();
+ if (roleContainer instanceof RealmModel) {
+ RealmModel realm = (RealmModel)roleContainer;
+ roleEntity.setRealmId(realm.getId());
+ } else {
+ ApplicationModel appModel = (ApplicationModel)roleContainer;
+ roleEntity.setApplicationId(appModel.getId());
+ }
+
+ List<String> compositeRolesIds = null;
+ for (RoleModel composite : roleModel.getComposites()) {
+
+ // Lazy init
+ if (compositeRolesIds == null) {
+ compositeRolesIds = new ArrayList<String>();
+ }
+
+ compositeRolesIds.add(composite.getId());
+ }
+ roleEntity.setCompositeRoleIds(compositeRolesIds);
+ }
+
+ this.exportWriter.writeEntities(fileName, result);
+
+ logger.infof("%d roles exported: ", result.size());
+ if (logger.isDebugEnabled()) {
+ logger.debug("Exported roles: " + result);
+ }
+ }
+
+ protected void exportUsers(KeycloakSession keycloakSession, String fileName) {
+ List<RealmModel> realms = keycloakSession.getRealms();
+ List<UserEntity> result = new LinkedList<UserEntity>();
+
+ for (RealmModel realm : realms) {
+ List<UserModel> userModels = realm.getUsers();
+ for (UserModel userModel : userModels) {
+ UserEntity userEntity = new UserEntity();
+ userEntity.setId(userModel.getId());
+ result.add(userEntity);
+
+ this.propertiesManager.setBasicPropertiesFromModel(userModel, userEntity);
+
+ userEntity.setLoginName(userModel.getLoginName());
+ userEntity.setRealmId(realm.getId());
+
+ // authentication links
+ AuthenticationLinkModel authLink = realm.getAuthenticationLink(userModel);
+ if (authLink != null) {
+ AuthenticationLinkEntity authLinkEntity = new AuthenticationLinkEntity();
+ this.propertiesManager.setBasicPropertiesFromModel(authLink, authLinkEntity);
+
+ userEntity.setAuthenticationLink(authLinkEntity);
+ }
+
+ // social links
+ Set<SocialLinkModel> socialLinks = realm.getSocialLinks(userModel);
+ if (socialLinks != null && !socialLinks.isEmpty()) {
+ List<SocialLinkEntity> socialLinkEntities = new ArrayList<SocialLinkEntity>();
+ for (SocialLinkModel socialLink : socialLinks) {
+ SocialLinkEntity socialLinkEntity = new SocialLinkEntity();
+ this.propertiesManager.setBasicPropertiesFromModel(socialLink, socialLinkEntity);
+
+ socialLinkEntities.add(socialLinkEntity);
+ }
+
+ userEntity.setSocialLinks(socialLinkEntities);
+ }
+
+ // required actions
+ Set<UserModel.RequiredAction> requiredActions = userModel.getRequiredActions();
+ if (requiredActions != null && !requiredActions.isEmpty()) {
+ userEntity.setRequiredActions(new ArrayList<UserModel.RequiredAction>(requiredActions));
+ }
+
+ // attributes
+ userEntity.setAttributes(userModel.getAttributes());
+
+ // roleIds
+ Set<RoleModel> roles = realm.getRoleMappings(userModel);
+ List<String> roleIds = new ArrayList<String>();
+ for (RoleModel role : roles) {
+ roleIds.add(role.getId());
+ }
+ userEntity.setRoleIds(roleIds);
+
+ // credentials
+ List<UserCredentialValueModel> credentials = realm.getCredentialsDirectly(userModel);
+ List<CredentialEntity> credEntities = new ArrayList<CredentialEntity>();
+ for (UserCredentialValueModel credModel : credentials) {
+ CredentialEntity credEntity = new CredentialEntity();
+ this.propertiesManager.setBasicPropertiesFromModel(credModel, credEntity);
+ credEntities.add(credEntity);
+ }
+
+ userEntity.setCredentials(credEntities);
+ }
+ }
+
+ this.exportWriter.writeEntities(fileName, result);
+
+ logger.infof("%d users exported: ", result.size());
+ if (logger.isDebugEnabled()) {
+ logger.debug("Exported users: " + result);
+ }
+ }
+
+
+ // Does it makes sense to export user failures ?
+ protected void exportUserFailures(KeycloakSession keycloakSession, String fileName) {
+ List<RealmModel> realms = keycloakSession.getRealms();
+ List<UsernameLoginFailureModel> allFailures = new ArrayList<UsernameLoginFailureModel>();
+ for (RealmModel realmModel : realms) {
+ allFailures.addAll(realmModel.getAllUserLoginFailures());
+ }
+
+ List<UsernameLoginFailureEntity> result = new LinkedList<UsernameLoginFailureEntity>();
+ for (UsernameLoginFailureModel failureModel : allFailures) {
+ UsernameLoginFailureEntity failureEntity = new UsernameLoginFailureEntity();
+ this.propertiesManager.setBasicPropertiesFromModel(failureModel, failureEntity);
+ result.add(failureEntity);
+
+ failureEntity.setUsername(failureModel.getUsername());
+ failureEntity.setNumFailures(failureModel.getNumFailures());
+ }
+
+ this.exportWriter.writeEntities(fileName, result);
+ }
+
+ private List<String> getScopeIds(ClientModel clientModel) {
+ Set<RoleModel> allScopes = clientModel.getRealm().getScopeMappings(clientModel);
+ List<String> scopeIds = new ArrayList<String>();
+ for (RoleModel role : allScopes) {
+ scopeIds.add(role.getId());
+ }
+ return scopeIds;
+ }
+
+ private List<ApplicationModel> getAllApplications(KeycloakSession keycloakSession) {
+ List<RealmModel> realms = keycloakSession.getRealms();
+ List<ApplicationModel> allApplications = new ArrayList<ApplicationModel>();
+ for (RealmModel realmModel : realms) {
+ allApplications.addAll(realmModel.getApplications());
+ }
+ return allApplications;
+ }
+
+ private List<RoleModel> getAllRoles(KeycloakSession keycloakSession) {
+ List<RoleModel> allRoles = new ArrayList<RoleModel>();
+
+ List<RealmModel> realms = keycloakSession.getRealms();
+ for (RealmModel realmModel : realms) {
+ allRoles.addAll(realmModel.getRoles());
+ }
+
+ List<ApplicationModel> allApplications = getAllApplications(keycloakSession);
+ for (ApplicationModel appModel : allApplications) {
+ allRoles.addAll(appModel.getRoles());
+ }
+
+ return allRoles;
+ }
+
+}
diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelImporter.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelImporter.java
new file mode 100644
index 0000000..b751d73
--- /dev/null
+++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelImporter.java
@@ -0,0 +1,329 @@
+package org.keycloak.exportimport;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.jboss.logging.Logger;
+import org.keycloak.exportimport.io.ImportReader;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.SocialLinkModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.entities.ApplicationEntity;
+import org.keycloak.models.entities.AuthenticationLinkEntity;
+import org.keycloak.models.entities.AuthenticationProviderEntity;
+import org.keycloak.models.entities.ClientEntity;
+import org.keycloak.models.entities.CredentialEntity;
+import org.keycloak.models.entities.OAuthClientEntity;
+import org.keycloak.models.entities.RealmEntity;
+import org.keycloak.models.entities.RequiredCredentialEntity;
+import org.keycloak.models.entities.RoleEntity;
+import org.keycloak.models.entities.SocialLinkEntity;
+import org.keycloak.models.entities.UserEntity;
+import org.keycloak.models.entities.UsernameLoginFailureEntity;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ModelImporter {
+
+ private static final Logger logger = Logger.getLogger(ModelImporter.class);
+
+ private ImportReader importReader;
+ private ExportImportPropertiesManager propertiesManager;
+
+ public void importModel(KeycloakSession keycloakSession, ImportReader importReader) {
+ // Initialize needed objects
+ this.importReader = importReader;
+ this.propertiesManager = new ExportImportPropertiesManager();
+
+ // Delete all the data from current model
+ keycloakSession.removeAllData();
+
+ importRealms(keycloakSession, "realms.json");
+ importApplications(keycloakSession, "applications.json");
+ importRoles(keycloakSession, "roles.json");
+
+ // Now we have all realms,applications and roles filled. So fill other objects (default roles, scopes etc)
+ importRealmsStep2(keycloakSession, "realms.json");
+ importApplicationsStep2(keycloakSession, "applications.json");
+
+ importOAuthClients(keycloakSession, "oauthClients.json");
+ importUsers(keycloakSession, "users.json");
+ importUserFailures(keycloakSession, "userFailures.json");
+
+ this.importReader.closeImportReader();
+ }
+
+ protected void importRealms(KeycloakSession keycloakSession, String fileName) {
+ List<RealmEntity> realms = this.importReader.readEntities(fileName, RealmEntity.class);
+
+ for (RealmEntity realmEntity : realms) {
+ RealmModel realm = keycloakSession.createRealm(realmEntity.getId(), realmEntity.getName());
+
+ this.propertiesManager.setBasicPropertiesToModel(realm, realmEntity);
+
+ Set<String> reqCredModels = new HashSet<String>();
+ for (RequiredCredentialEntity requiredCredEntity : realmEntity.getRequiredCredentials()) {
+ reqCredModels.add(requiredCredEntity.getType());
+ }
+ realm.updateRequiredCredentials(reqCredModels);
+
+ // AdminApp and defaultRoles are set in step2
+
+ // password policy
+ realm.setPasswordPolicy(new PasswordPolicy(realmEntity.getPasswordPolicy()));
+
+ // authentication providers
+ List<AuthenticationProviderModel> authProviderModels = new ArrayList<AuthenticationProviderModel>();
+ for (AuthenticationProviderEntity authProviderEntity : realmEntity.getAuthenticationProviders()) {
+ AuthenticationProviderModel authProvider = new AuthenticationProviderModel();
+ this.propertiesManager.setBasicPropertiesToModel(authProvider, authProviderEntity);
+ authProviderModels.add(authProvider);
+
+ }
+ realm.setAuthenticationProviders(authProviderModels);
+ }
+
+ logger.infof("Realms imported: " + realms);
+ }
+
+ protected void importApplications(KeycloakSession keycloakSession, String fileName) {
+ List<ApplicationEntity> apps = this.importReader.readEntities(fileName, ApplicationEntity.class);
+ for (ApplicationEntity appEntity : apps) {
+ RealmModel realm = keycloakSession.getRealm(appEntity.getRealmId());
+ ApplicationModel app = realm.addApplication(appEntity.getId(), appEntity.getName());
+
+ this.propertiesManager.setBasicPropertiesToModel(app , appEntity);
+
+ // scopeIds and default roles will be done in step2
+ }
+
+ logger.infof("Applications imported: " + apps);
+ }
+
+ protected void importRoles(KeycloakSession keycloakSession, String fileName) {
+ // helper map for composite roles
+ Map<String, RoleEntity> rolesMap = new HashMap<String, RoleEntity>();
+
+ List<RoleEntity> roles = this.importReader.readEntities(fileName, RoleEntity.class);
+ for (RoleEntity roleEntity : roles) {
+ RoleModel role = null;
+ if (roleEntity.getRealmId() != null) {
+ RealmModel realm = keycloakSession.getRealm(roleEntity.getRealmId());
+ role = realm.addRole(roleEntity.getId(), roleEntity.getName());
+ } else if (roleEntity.getApplicationId() != null) {
+ ApplicationModel app = findApplicationById(keycloakSession, roleEntity.getApplicationId());
+ role = app.addRole(roleEntity.getId(), roleEntity.getName());
+ } else {
+ throw new IllegalStateException("Role " + roleEntity.getId() + " doesn't have realmId nor applicationId");
+ }
+
+ role.setDescription(roleEntity.getDescription());
+
+ rolesMap.put(roleEntity.getId(), roleEntity);
+ }
+
+ // All roles were added. Fill composite roles now
+ for (RealmModel realm : keycloakSession.getRealms()) {
+
+ // realm roles
+ fillCompositeRoles(rolesMap, realm, realm);
+
+ // app roles
+ for (ApplicationModel app : realm.getApplications()) {
+ fillCompositeRoles(rolesMap, app, realm);
+ }
+ }
+
+ logger.infof("%d roles imported: ", roles);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Imported roles: " + roles);
+ }
+ }
+
+ private void fillCompositeRoles(Map<String, RoleEntity> rolesMap, RoleContainerModel roleContainer, RealmModel realm) {
+ for (RoleModel role : roleContainer.getRoles()) {
+ RoleEntity roleEntity = rolesMap.get(role.getId());
+
+ if (roleEntity.getCompositeRoleIds() == null) {
+ continue;
+ }
+
+ for (String compositeRoleId : roleEntity.getCompositeRoleIds()) {
+ RoleModel compositeRole = realm.getRoleById(compositeRoleId);
+ role.addCompositeRole(compositeRole);
+ }
+ }
+ }
+
+ protected void importRealmsStep2(KeycloakSession keycloakSession, String fileName) {
+ List<RealmEntity> realms = this.importReader.readEntities(fileName, RealmEntity.class);
+ RealmModel adminRealm = keycloakSession.getRealm(Config.getAdminRealm());
+
+ for (RealmEntity realmEntity : realms) {
+ RealmModel realm = keycloakSession.getRealm(realmEntity.getId());
+
+ // admin app
+ String adminAppId = realmEntity.getAdminAppId();
+ if (adminAppId != null) {
+ realm.setAdminApp(adminRealm.getApplicationById(adminAppId));
+ }
+
+ // Default roles
+ realm.updateDefaultRoles(realmEntity.getDefaultRoles().toArray(new String[] {}));
+ }
+ }
+
+ protected void importApplicationsStep2(KeycloakSession keycloakSession, String fileName) {
+ List<ApplicationEntity> apps = this.importReader.readEntities(fileName, ApplicationEntity.class);
+ for (ApplicationEntity appEntity : apps) {
+ RealmModel realm = keycloakSession.getRealm(appEntity.getRealmId());
+ ApplicationModel application = realm.getApplicationById(appEntity.getId());
+
+ // Default roles
+ application.updateDefaultRoles(appEntity.getDefaultRoles().toArray(new String[] {}));
+
+ // Scopes
+ addScopes(realm, application, appEntity);
+ }
+ }
+
+ private void addScopes(RealmModel realm, ClientModel client, ClientEntity clientEntity) {
+ for (String scopeId : clientEntity.getScopeIds()) {
+ RoleModel scope = realm.getRoleById(scopeId);
+ realm.addScopeMapping(client, scope);
+ }
+ }
+
+ protected void importOAuthClients(KeycloakSession keycloakSession, String fileName) {
+ List<OAuthClientEntity> clients = this.importReader.readEntities(fileName, OAuthClientEntity.class);
+ for (OAuthClientEntity clientEntity : clients) {
+ RealmModel realm = keycloakSession.getRealm(clientEntity.getRealmId());
+ OAuthClientModel client = realm.addOAuthClient(clientEntity.getId(), clientEntity.getName());
+
+ this.propertiesManager.setBasicPropertiesToModel(client, clientEntity);
+
+ client.setClientId(clientEntity.getName());
+
+ // Scopes. All roles are already added at this point
+ addScopes(realm, client, clientEntity);
+ }
+
+ logger.infof("OAuth clients imported: " + clients);
+ }
+
+ protected ApplicationModel findApplicationById(KeycloakSession keycloakSession, String applicationId) {
+ for (RealmModel realm : keycloakSession.getRealms()) {
+ ApplicationModel appModel = realm.getApplicationById(applicationId);
+ if (appModel != null) {
+ return appModel;
+ }
+ }
+
+ return null;
+ }
+
+ public void importUsers(KeycloakSession keycloakSession, String fileName) {
+ List<UserEntity> users = this.importReader.readEntities(fileName, UserEntity.class);
+ for (UserEntity userEntity : users) {
+ RealmModel realm = keycloakSession.getRealm(userEntity.getRealmId());
+ UserModel user = realm.addUser(userEntity.getId(), userEntity.getLoginName());
+
+ // We need to remove defaultRoles here as realm.addUser is automatically adding them. We may add them later during roles mapping processing
+ for (RoleModel role : realm.getRoleMappings(user)) {
+ realm.deleteRoleMapping(user, role);
+ }
+
+ this.propertiesManager.setBasicPropertiesToModel(user, userEntity);
+
+ // authentication links
+ AuthenticationLinkEntity authLinkEntity = userEntity.getAuthenticationLink();
+ if (authLinkEntity != null) {
+ AuthenticationLinkModel authLinkModel = new AuthenticationLinkModel();
+ this.propertiesManager.setBasicPropertiesToModel(authLinkModel, authLinkEntity);
+
+ realm.setAuthenticationLink(user, authLinkModel);
+ }
+
+ // social links
+ List<SocialLinkEntity> socialLinks = userEntity.getSocialLinks();
+ if (socialLinks != null && !socialLinks.isEmpty()) {
+ for (SocialLinkEntity socialLinkEntity : socialLinks) {
+ SocialLinkModel socialLink = new SocialLinkModel();
+ this.propertiesManager.setBasicPropertiesToModel(socialLink, socialLinkEntity);
+
+ realm.addSocialLink(user, socialLink);
+ }
+ }
+
+ // required actions
+ List<UserModel.RequiredAction> requiredActions = userEntity.getRequiredActions();
+ if (requiredActions != null && !requiredActions.isEmpty()) {
+ for (UserModel.RequiredAction reqAction : requiredActions) {
+ user.addRequiredAction(reqAction);
+ }
+ }
+
+ // attributes
+ if (userEntity.getAttributes() != null) {
+ for (Map.Entry<String, String> attr : userEntity.getAttributes().entrySet()) {
+ user.setAttribute(attr.getKey(), attr.getValue());
+ }
+ }
+
+ // roles
+ if (userEntity.getRoleIds() != null) {
+ for (String roleId : userEntity.getRoleIds()) {
+ RoleModel role = realm.getRoleById(roleId);
+ realm.grantRole(user, role);
+ }
+ }
+
+ // credentials
+ List<CredentialEntity> credentials = userEntity.getCredentials();
+ if (credentials != null) {
+ for (CredentialEntity credEntity : credentials) {
+ UserCredentialValueModel credModel = new UserCredentialValueModel();
+ this.propertiesManager.setBasicPropertiesToModel(credModel, credEntity);
+
+ realm.updateCredentialDirectly(user, credModel);
+ }
+ }
+ }
+
+ logger.infof("%d users imported: ", users.size());
+ if (logger.isDebugEnabled()) {
+ logger.debug("Imported users: " + users);
+ }
+ }
+
+ public void importUserFailures(KeycloakSession keycloakSession, String fileName) {
+ List<UsernameLoginFailureEntity> userFailures = this.importReader.readEntities(fileName, UsernameLoginFailureEntity.class);
+ for (UsernameLoginFailureEntity entity : userFailures) {
+ RealmModel realm = keycloakSession.getRealm(entity.getRealmId());
+ UsernameLoginFailureModel model = realm.addUserLoginFailure(entity.getUsername());
+
+ this.propertiesManager.setBasicPropertiesToModel(model , entity);
+
+ for (int i=0 ; i<entity.getNumFailures() ; i++) {
+ model.incrementFailures();
+ }
+ }
+ }
+}
diff --git a/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportImportProvider b/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportImportProvider
new file mode 100644
index 0000000..353987f
--- /dev/null
+++ b/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportImportProvider
@@ -0,0 +1 @@
+org.keycloak.exportimport.ExportImportProviderImpl
diff --git a/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.io.ExportImportIOProvider b/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.io.ExportImportIOProvider
new file mode 100644
index 0000000..75c97ec
--- /dev/null
+++ b/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.io.ExportImportIOProvider
@@ -0,0 +1,2 @@
+org.keycloak.exportimport.io.directory.TmpDirExportImportIOProvider
+org.keycloak.exportimport.io.zip.EncryptedZIPIOProvider
diff --git a/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/ExportImportTestBase.java b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/ExportImportTestBase.java
new file mode 100644
index 0000000..1e39f24
--- /dev/null
+++ b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/ExportImportTestBase.java
@@ -0,0 +1,110 @@
+package org.keycloak.exportimport;
+
+import java.util.Iterator;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.model.test.AbstractModelTest;
+import org.keycloak.model.test.ImportTest;
+import org.keycloak.models.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.ApplianceBootstrap;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.resources.KeycloakApplication;
+import org.keycloak.util.ProviderLoader;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class ExportImportTestBase {
+
+ protected KeycloakSessionFactory factory;
+
+ protected KeycloakSession identitySession;
+ protected RealmManager realmManager;
+
+ @Test
+ public void testExportImport() throws Exception {
+ // Init JPA model
+ Config.setModelProvider(getExportModelProvider());
+ factory = KeycloakApplication.createSessionFactory();
+
+ // Bootstrap admin realm
+ beginTransaction();
+ new ApplianceBootstrap().bootstrap(identitySession, "/auth");
+ commitTransaction();
+
+ // Classic import of realm to JPA model
+ beginTransaction();
+ RealmRepresentation rep = AbstractModelTest.loadJson("testrealm.json");
+ realmManager = new RealmManager(identitySession);
+ RealmModel realm = realmManager.createRealm("demo", rep.getRealm());
+ realmManager.importRealm(rep, realm);
+
+ commitTransaction();
+
+ // Full export of realm
+ exportModel(factory);
+
+ beginTransaction();
+ realm = identitySession.getRealm("demo");
+ String wburkeId = realm.getUser("wburke").getId();
+ String appId = realm.getApplicationByName("Application").getId();
+
+ // Commit transaction and close JPA now
+ commitTransaction();
+ factory.close();
+
+ // Bootstrap mongo session and factory
+ Config.setModelProvider(getImportModelProvider());
+ factory = KeycloakApplication.createSessionFactory();
+
+ // Full import of previous export into mongo
+ importModel(factory);
+
+ // Verify it's imported in mongo (reusing ImportTest)
+ beginTransaction();
+ RealmModel importedRealm = identitySession.getRealm("demo");
+ System.out.println("Exported realm: " + realm + ", Imported realm: " + importedRealm);
+
+ Assert.assertEquals(wburkeId, importedRealm.getUser("wburke").getId());
+ Assert.assertEquals(appId, importedRealm.getApplicationByName("Application").getId());
+ ImportTest.assertDataImportedInRealm(importedRealm);
+
+ // Commit and close Mongo
+ commitTransaction();
+ factory.close();
+ }
+
+ protected abstract String getExportModelProvider();
+
+ protected abstract String getImportModelProvider();
+
+ protected abstract void exportModel(KeycloakSessionFactory factory);
+
+ protected abstract void importModel(KeycloakSessionFactory factory);
+
+ protected void beginTransaction() {
+ identitySession = factory.createSession();
+ identitySession.getTransaction().begin();
+ realmManager = new RealmManager(identitySession);
+ }
+
+ protected void commitTransaction() {
+ identitySession.getTransaction().commit();
+ identitySession.close();
+ }
+
+ protected ExportImportProvider getExportImportProvider() {
+ Iterator<ExportImportProvider> providers = ProviderLoader.load(ExportImportProvider.class).iterator();
+
+ if (providers.hasNext()) {
+ return providers.next();
+ } else {
+ throw new IllegalStateException("ExportImportProvider not found");
+ }
+ }
+}
diff --git a/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/JPAToMongoExportImportTest.java b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/JPAToMongoExportImportTest.java
new file mode 100644
index 0000000..8175bdb
--- /dev/null
+++ b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/JPAToMongoExportImportTest.java
@@ -0,0 +1,37 @@
+package org.keycloak.exportimport;
+
+import org.keycloak.exportimport.io.directory.TmpDirExportImportIOProvider;
+import org.keycloak.models.Config;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * Test for full export of data from JPA and import them to Mongo. Using "directory" provider
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JPAToMongoExportImportTest extends ExportImportTestBase {
+
+ @Override
+ protected String getExportModelProvider() {
+ return "jpa";
+ }
+
+ @Override
+ protected String getImportModelProvider() {
+ return "mongo";
+ }
+
+ @Override
+ protected void exportModel(KeycloakSessionFactory factory) {
+ Config.setExportImportAction(ExportImportProviderImpl.ACTION_EXPORT);
+ Config.setExportImportProvider(TmpDirExportImportIOProvider.PROVIDER_ID);
+ getExportImportProvider().checkExportImport(factory);
+ }
+
+ @Override
+ protected void importModel(KeycloakSessionFactory factory) {
+ Config.setExportImportAction(ExportImportProviderImpl.ACTION_IMPORT);
+ Config.setExportImportProvider(TmpDirExportImportIOProvider.PROVIDER_ID);
+ getExportImportProvider().checkExportImport(factory);
+ }
+}
diff --git a/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/MongoToJPAExportImportTest.java b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/MongoToJPAExportImportTest.java
new file mode 100644
index 0000000..e715c9c
--- /dev/null
+++ b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/MongoToJPAExportImportTest.java
@@ -0,0 +1,70 @@
+package org.keycloak.exportimport;
+
+import java.io.File;
+
+import org.junit.Assert;
+import org.keycloak.exportimport.io.zip.EncryptedZIPIOProvider;
+import org.keycloak.models.Config;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * Test for full export of data from Mongo and import them to JPA. Using export into encrypted ZIP and import from it
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MongoToJPAExportImportTest extends ExportImportTestBase {
+
+ private static final String zipFile = "keycloak-export.zip";
+
+ @Override
+ protected String getExportModelProvider() {
+ return "mongo";
+ }
+
+ @Override
+ protected String getImportModelProvider() {
+ return "jpa";
+ }
+
+ @Override
+ protected void exportModel(KeycloakSessionFactory factory) {
+ Config.setExportImportAction(ExportImportProviderImpl.ACTION_EXPORT);
+ Config.setExportImportProvider(EncryptedZIPIOProvider.PROVIDER_ID);
+ File zipFile = getZipFile();
+ Config.setExportImportZipFile(zipFile.getAbsolutePath());
+ Config.setExportImportZipPassword("password123");
+
+ if (zipFile.exists()) {
+ zipFile.delete();
+ }
+
+ new ExportImportProviderImpl().checkExportImport(factory);
+ }
+
+ @Override
+ protected void importModel(KeycloakSessionFactory factory) {
+ Config.setExportImportAction(ExportImportProviderImpl.ACTION_IMPORT);
+ Config.setExportImportProvider(EncryptedZIPIOProvider.PROVIDER_ID);
+ File zipFile = getZipFile();
+ Config.setExportImportZipFile(zipFile.getAbsolutePath());
+ Config.setExportImportZipPassword("password-invalid");
+
+ // Try invalid password
+ try {
+ new ExportImportProviderImpl().checkExportImport(factory);
+ Assert.fail("Not expected to be here. Exception should be thrown");
+ } catch (Exception e) {};
+
+ Config.setExportImportZipPassword("password123");
+ new ExportImportProviderImpl().checkExportImport(factory);
+
+ if (zipFile.exists()) {
+ zipFile.delete();
+ }
+ }
+
+ private File getZipFile() {
+ String tempDir = System.getProperty("java.io.tmpdir");
+ return new File(tempDir + File.separator + "keycloak-export.zip");
+ }
+}
export-import/pom.xml 22(+22 -0)
diff --git a/export-import/pom.xml b/export-import/pom.xml
new file mode 100644
index 0000000..6df4145
--- /dev/null
+++ b/export-import/pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-1-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>pom</packaging>
+
+ <artifactId>keycloak-export-import</artifactId>
+ <name>Keycloak Export And Import</name>
+ <description/>
+
+ <modules>
+ <module>export-import-api</module>
+ <module>export-import-impl</module>
+ </modules>
+
+</project>
diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationLinkModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationLinkModel.java
index 1a4c52d..8cc0bd8 100644
--- a/model/api/src/main/java/org/keycloak/models/AuthenticationLinkModel.java
+++ b/model/api/src/main/java/org/keycloak/models/AuthenticationLinkModel.java
@@ -7,8 +7,10 @@ package org.keycloak.models;
*/
public class AuthenticationLinkModel {
- private final String authProvider;
- private final String authUserId;
+ private String authProvider;
+ private String authUserId;
+
+ public AuthenticationLinkModel() {};
public AuthenticationLinkModel(String authProvider, String authUserId) {
this.authProvider = authProvider;
@@ -19,7 +21,15 @@ public class AuthenticationLinkModel {
return authUserId;
}
+ public void setAuthUserId(String authUserId) {
+ this.authUserId = authUserId;
+ }
+
public String getAuthProvider() {
return authProvider;
}
+
+ public void setAuthProvider(String authProvider) {
+ this.authProvider = authProvider;
+ }
}
diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java
index 7dea3c9..8ef7abb 100644
--- a/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java
@@ -14,6 +14,8 @@ public class AuthenticationProviderModel {
private boolean passwordUpdateSupported = true;
private Map<String, String> config;
+ public AuthenticationProviderModel() {};
+
public AuthenticationProviderModel(String providerName, boolean passwordUpdateSupported, Map<String, String> config) {
this.providerName = providerName;
this.passwordUpdateSupported = passwordUpdateSupported;
diff --git a/model/api/src/main/java/org/keycloak/models/Config.java b/model/api/src/main/java/org/keycloak/models/Config.java
index 2655eb3..e50ed9a 100644
--- a/model/api/src/main/java/org/keycloak/models/Config.java
+++ b/model/api/src/main/java/org/keycloak/models/Config.java
@@ -33,6 +33,16 @@ public class Config {
public static final String TIMER_PROVIDER_KEY = "keycloak.timer";
public static final String TIMER_PROVIDER_DEFAULT = "basic";
+ public static final String EXPORT_IMPORT_ACTION = "keycloak.migration.action";
+ public static final String EXPORT_IMPORT_PROVIDER = "keycloak.migration.provider";
+ public static final String EXPORT_IMPORT_PROVIDER_DEFAULT = "zip";
+ // used for "directory" provider
+ public static final String EXPORT_IMPORT_DIR = "keycloak.migration.dir";
+ // used for "zip" provider
+ public static final String EXPORT_IMPORT_ZIP_FILE = "keycloak.migration.zipFile";
+ public static final String EXPORT_IMPORT_ZIP_PASSWORD = "keycloak.migration.zipPassword";
+
+
public static String getAdminRealm() {
return System.getProperty(ADMIN_REALM_KEY, ADMIN_REALM_DEFAULT);
}
@@ -117,4 +127,45 @@ public class Config {
System.setProperty(THEME_ADMIN_KEY, adminTheme);
}
+ // EXPORT + IMPORT
+
+ public static String getExportImportAction() {
+ return System.getProperty(EXPORT_IMPORT_ACTION);
+ }
+
+ public static void setExportImportAction(String exportImportAction) {
+ System.setProperty(EXPORT_IMPORT_ACTION, exportImportAction);
+ }
+
+ public static String getExportImportProvider() {
+ return System.getProperty(EXPORT_IMPORT_PROVIDER, EXPORT_IMPORT_PROVIDER_DEFAULT);
+ }
+
+ public static void setExportImportProvider(String exportImportProvider) {
+ System.setProperty(EXPORT_IMPORT_PROVIDER, exportImportProvider);
+ }
+
+ public static String getExportImportDir() {
+ return System.getProperty(EXPORT_IMPORT_DIR);
+ }
+
+ public static void setExportImportDir(String exportImportDir) {
+ System.setProperty(EXPORT_IMPORT_DIR, exportImportDir);
+ }
+
+ public static String getExportImportZipFile() {
+ return System.getProperty(EXPORT_IMPORT_ZIP_FILE);
+ }
+
+ public static void setExportImportZipFile(String exportImportZipFile) {
+ System.setProperty(EXPORT_IMPORT_ZIP_FILE, exportImportZipFile);
+ }
+
+ public static String getExportImportZipPassword() {
+ return System.getProperty(EXPORT_IMPORT_ZIP_PASSWORD);
+ }
+
+ public static void setExportImportZipPassword(String exportImportZipPassword) {
+ System.setProperty(EXPORT_IMPORT_ZIP_PASSWORD, exportImportZipPassword);
+ }
}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/OAuthClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/OAuthClientEntity.java
new file mode 100644
index 0000000..368fcf7
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/OAuthClientEntity.java
@@ -0,0 +1,7 @@
+package org.keycloak.models.entities;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OAuthClientEntity extends ClientEntity {
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RoleEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RoleEntity.java
new file mode 100644
index 0000000..a961c70
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/RoleEntity.java
@@ -0,0 +1,57 @@
+package org.keycloak.models.entities;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RoleEntity extends AbstractIdentifiableEntity {
+
+ private String name;
+ private String description;
+
+ private List<String> compositeRoleIds;
+
+ private String realmId;
+ private String applicationId;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public List<String> getCompositeRoleIds() {
+ return compositeRoleIds;
+ }
+
+ public void setCompositeRoleIds(List<String> compositeRoleIds) {
+ this.compositeRoleIds = compositeRoleIds;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getApplicationId() {
+ return applicationId;
+ }
+
+ public void setApplicationId(String applicationId) {
+ this.applicationId = applicationId;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/KeycloakSession.java b/model/api/src/main/java/org/keycloak/models/KeycloakSession.java
index 00b4db9..9aa55ee 100755
--- a/model/api/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/model/api/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -16,5 +16,7 @@ public interface KeycloakSession {
List<RealmModel> getRealms();
boolean removeRealm(String id);
+ void removeAllData();
+
void close();
}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 14d531e..1c73b2f 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -109,12 +109,18 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
void updateCredential(UserModel user, UserCredentialModel cred);
+ List<UserCredentialValueModel> getCredentialsDirectly(UserModel user);
+
+ void updateCredentialDirectly(UserModel user, UserCredentialValueModel cred);
+
UserModel getUser(String name);
UserModel getUserByEmail(String email);
UserModel getUserById(String name);
+ UserModel addUser(String id, String username);
+
UserModel addUser(String username);
boolean removeUser(String name);
@@ -135,6 +141,8 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
ApplicationModel addApplication(String name);
+ ApplicationModel addApplication(String id, String name);
+
boolean removeApplication(String id);
ApplicationModel getApplicationById(String id);
@@ -160,12 +168,13 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
void setSocial(boolean social);
- public boolean isUpdateProfileOnInitialSocialLogin();
+ boolean isUpdateProfileOnInitialSocialLogin();
- public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin);
+ void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin);
- public UsernameLoginFailureModel getUserLoginFailure(String username);
+ UsernameLoginFailureModel getUserLoginFailure(String username);
UsernameLoginFailureModel addUserLoginFailure(String username);
+ List<UsernameLoginFailureModel> getAllUserLoginFailures();
List<UserModel> getUsers();
@@ -175,6 +184,8 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
OAuthClientModel addOAuthClient(String name);
+ OAuthClientModel addOAuthClient(String id, String name);
+
OAuthClientModel getOAuthClient(String name);
OAuthClientModel getOAuthClientById(String id);
boolean removeOAuthClient(String id);
diff --git a/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java b/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java
index 62ecfdb..2cad371 100755
--- a/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java
@@ -12,6 +12,8 @@ public interface RoleContainerModel {
RoleModel addRole(String name);
+ RoleModel addRole(String id, String name);
+
boolean removeRole(RoleModel role);
Set<RoleModel> getRoles();
diff --git a/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java b/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java
index 76e8929..802fa9e 100755
--- a/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java
+++ b/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java
@@ -9,6 +9,8 @@ public class SocialLinkModel {
private String socialProvider;
private String socialUsername;
+ public SocialLinkModel() {};
+
public SocialLinkModel(String socialProvider, String socialUserId, String socialUsername) {
this.socialUserId = socialUserId;
this.socialProvider = socialProvider;
diff --git a/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java b/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java
new file mode 100644
index 0000000..3702274
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java
@@ -0,0 +1,46 @@
+package org.keycloak.models;
+
+/**
+ * Used just in cases when we want to "directly" update or retrieve the hash or salt of user credential (For example during export/import)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserCredentialValueModel {
+
+ private String type;
+ private String value;
+ private String device;
+ private byte[] salt;
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getDevice() {
+ return device;
+ }
+
+ public void setDevice(String device) {
+ this.device = device;
+ }
+
+ public byte[] getSalt() {
+ return salt;
+ }
+
+ public void setSalt(byte[] salt) {
+ this.salt = salt;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java
new file mode 100644
index 0000000..51e2a3a
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java
@@ -0,0 +1,23 @@
+package org.keycloak.models.utils.reflection;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+/**
+ * A criteria that matches a property based on its annotations
+ *
+ * @see PropertyCriteria
+ */
+public class AnnotatedPropertyCriteria implements PropertyCriteria {
+ private final Class<? extends Annotation> annotationClass;
+
+ public AnnotatedPropertyCriteria(Class<? extends Annotation> annotationClass) {
+ this.annotationClass = annotationClass;
+ }
+
+ @Override
+ public boolean methodMatches(Method m) {
+ return m.isAnnotationPresent(annotationClass);
+ }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java
new file mode 100644
index 0000000..868fdba
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java
@@ -0,0 +1,8 @@
+package org.keycloak.models.utils.reflection;
+
+import java.lang.reflect.Method;
+
+public interface MethodProperty<V> extends Property<V> {
+
+ Method getAnnotatedElement();
+}
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodPropertyImpl.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodPropertyImpl.java
new file mode 100644
index 0000000..16cc165
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodPropertyImpl.java
@@ -0,0 +1,220 @@
+package org.keycloak.models.utils.reflection;
+
+import java.beans.Introspector;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+
+/**
+ * A bean property based on the value represented by a getter/setter method pair
+ */
+class MethodPropertyImpl<V> implements MethodProperty<V> {
+ private static final String GETTER_METHOD_PREFIX = "get";
+ private static final String SETTER_METHOD_PREFIX = "set";
+ private static final String BOOLEAN_GETTER_METHOD_PREFIX = "is";
+
+ private static final int GETTER_METHOD_PREFIX_LENGTH = GETTER_METHOD_PREFIX.length();
+ private static final int SETTER_METHOD_PREFIX_LENGTH = SETTER_METHOD_PREFIX.length();
+ private static final int BOOLEAN_GETTER_METHOD_PREFIX_LENGTH = BOOLEAN_GETTER_METHOD_PREFIX.length();
+
+ private final Method getterMethod;
+ private final String propertyName;
+ private final Method setterMethod;
+
+ public MethodPropertyImpl(Method method) {
+ final String accessorMethodPrefix;
+ final String propertyNameInAccessorMethod;
+
+ if (method.getName().startsWith(GETTER_METHOD_PREFIX)) {
+ if (method.getReturnType() == Void.TYPE) {
+ throw new IllegalArgumentException(
+ "Invalid accessor method, must have return value if starts with 'get'. Method: " + method);
+ } else if (method.getParameterTypes().length > 0) {
+ throw new IllegalArgumentException(
+ "Invalid accessor method, must have zero arguments if starts with 'get'. Method: " + method);
+ }
+ propertyNameInAccessorMethod = method.getName().substring(GETTER_METHOD_PREFIX_LENGTH);
+ accessorMethodPrefix = GETTER_METHOD_PREFIX;
+ } else if (method.getName().startsWith(SETTER_METHOD_PREFIX)) {
+ if (method.getReturnType() != Void.TYPE) {
+ throw new IllegalArgumentException(
+ "Invalid accessor method, must not have return value if starts with 'set'. Method: " + method);
+ } else if (method.getParameterTypes().length != 1) {
+ throw new IllegalArgumentException(
+ "Invalid accessor method, must have one argument if starts with 'set'. Method: " + method);
+ }
+ propertyNameInAccessorMethod = method.getName().substring(SETTER_METHOD_PREFIX_LENGTH);
+ accessorMethodPrefix = SETTER_METHOD_PREFIX;
+ } else if (method.getName().startsWith(BOOLEAN_GETTER_METHOD_PREFIX)) {
+ if (method.getReturnType() != Boolean.TYPE || !method.getReturnType().isPrimitive()) {
+ throw new IllegalArgumentException(
+ "Invalid accessor method, must return boolean primitive if starts with 'is'. Method: " +
+ method);
+ }
+ propertyNameInAccessorMethod = method.getName().substring(BOOLEAN_GETTER_METHOD_PREFIX_LENGTH);
+ accessorMethodPrefix = BOOLEAN_GETTER_METHOD_PREFIX;
+ } else {
+ throw new IllegalArgumentException(
+ "Invalid accessor method, must start with 'get', 'set' or 'is'. " + "Method: " + method);
+ }
+
+ if (propertyNameInAccessorMethod.length() == 0 ||
+ !Character.isUpperCase(propertyNameInAccessorMethod.charAt(0))) {
+ throw new IllegalArgumentException("Invalid accessor method, prefix '" + accessorMethodPrefix +
+ "' must be followed a non-empty property name, capitalized. Method: " + method);
+ }
+
+ this.propertyName = Introspector.decapitalize(propertyNameInAccessorMethod);
+ this.getterMethod = getGetterMethod(method.getDeclaringClass(), propertyName);
+ this.setterMethod = getSetterMethod(method.getDeclaringClass(), propertyName);
+ }
+
+ @Override
+ public String getName() {
+ return propertyName;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Class<V> getJavaClass() {
+ return (Class<V>) getterMethod.getReturnType();
+ }
+
+ @Override
+ public Type getBaseType() {
+ return getterMethod.getGenericReturnType();
+ }
+
+ @Override
+ public Method getAnnotatedElement() {
+ return getterMethod;
+ }
+
+ @Override
+ public Member getMember() {
+ return getterMethod;
+ }
+
+ @Override
+ public V getValue(Object instance) {
+ if (getterMethod == null) {
+ throw new UnsupportedOperationException("Property " +
+ this.setterMethod.getDeclaringClass() + "." + propertyName +
+ " cannot be read, as there is no getter method.");
+ }
+ return Reflections.cast(Reflections.invokeMethod(getterMethod, instance));
+ }
+
+ @Override
+ public void setValue(Object instance, V value) {
+ if (setterMethod == null) {
+ // if the setter method is null may be because the declaring type is an interface which does not declare
+ // a setter method. We just check if the instance is assignable from the property declaring class and
+ // try to find a overridden method.
+ if (getDeclaringClass().isAssignableFrom(instance.getClass())) {
+ Method instanceSetterMethod = getSetterMethod(instance.getClass(), getName());
+
+ if (instanceSetterMethod != null) {
+ Reflections.invokeMethod(instanceSetterMethod, instance, value);
+ return;
+ }
+ }
+
+ throw new UnsupportedOperationException("Property " +
+ this.getterMethod.getDeclaringClass() + "." + propertyName +
+ " is read only, as there is no setter method.");
+ }
+ Reflections.invokeMethod(setterMethod, instance, value);
+ }
+
+ private static Method getSetterMethod(Class<?> clazz, String name) {
+ Method[] methods = clazz.getMethods();
+ for (Method method : methods) {
+ String methodName = method.getName();
+ if (methodName.startsWith(SETTER_METHOD_PREFIX) && method.getParameterTypes().length == 1) {
+ if (Introspector.decapitalize(methodName.substring(SETTER_METHOD_PREFIX_LENGTH)).equals(name)) {
+ return method;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static Method getGetterMethod(Class<?> clazz, String name) {
+ for (Method method : clazz.getDeclaredMethods()) {
+ String methodName = method.getName();
+ if (method.getParameterTypes().length == 0) {
+ if (methodName.startsWith(GETTER_METHOD_PREFIX)) {
+ if (Introspector.decapitalize(methodName.substring(GETTER_METHOD_PREFIX_LENGTH)).equals(name)) {
+ return method;
+ }
+ } else if (methodName.startsWith(BOOLEAN_GETTER_METHOD_PREFIX)) {
+ if (Introspector.decapitalize(
+ methodName.substring(BOOLEAN_GETTER_METHOD_PREFIX_LENGTH)).equals(name)) {
+ return method;
+ }
+ }
+ }
+ }
+ throw new IllegalArgumentException("no such getter method: " + clazz.getName() + '.' + name);
+ }
+
+ @Override
+ public Class<?> getDeclaringClass() {
+ return getterMethod.getDeclaringClass();
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return setterMethod == null;
+ }
+
+ @Override
+ public void setAccessible() {
+ if (setterMethod != null) {
+ Reflections.setAccessible(setterMethod);
+ }
+ if (getterMethod != null) {
+ Reflections.setAccessible(getterMethod);
+ }
+ }
+
+ @Override
+ public boolean isAnnotationPresent(final Class<? extends Annotation> annotation) {
+ return getAnnotatedElement() != null && getAnnotatedElement().isAnnotationPresent(annotation);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ if (isReadOnly()) {
+ builder.append("read-only ").append(setterMethod.toString()).append("; ");
+ }
+ builder.append(getterMethod.toString());
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 1;
+ hash = hash * 31 + (setterMethod == null ? 0 : setterMethod.hashCode());
+ hash = hash * 31 + getterMethod.hashCode();
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MethodPropertyImpl<?>) {
+ MethodPropertyImpl<?> that = (MethodPropertyImpl<?>) obj;
+ if (this.setterMethod == null) {
+ return that.setterMethod == null && this.getterMethod.equals(that.getterMethod);
+ } else {
+ return this.setterMethod.equals(that.setterMethod) && this.getterMethod.equals(that.getterMethod);
+ }
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/Properties.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/Properties.java
new file mode 100644
index 0000000..3348d8b
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/Properties.java
@@ -0,0 +1,44 @@
+package org.keycloak.models.utils.reflection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+
+/**
+ * Utility class for working with JavaBean style properties
+ *
+ * @see Property
+ */
+public class Properties {
+
+ private Properties() {
+ }
+
+ /**
+ * Create a JavaBean style property from the specified method
+ *
+ * @param <V>
+ * @param method
+ *
+ * @return
+ *
+ * @throws IllegalArgumentException if the method does not match JavaBean conventions
+ * @see http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html
+ */
+ public static <V> MethodProperty<V> createProperty(Method method) {
+ return new MethodPropertyImpl<V>(method);
+ }
+
+ /**
+ * Indicates whether this method is a valid property method.
+ */
+ public static <V> boolean isProperty(Method method) {
+ try {
+ new MethodPropertyImpl<V>(method);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+}
+
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/Property.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/Property.java
new file mode 100644
index 0000000..6cf392d
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/Property.java
@@ -0,0 +1,105 @@
+package org.keycloak.models.utils.reflection;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Member;
+import java.lang.reflect.Type;
+
+/**
+ * A representation of a JavaBean style property
+ *
+ * @param <V> the type of the properties value
+ *
+ * @see Properties
+ */
+public interface Property<V> {
+
+ /**
+ * Returns the name of the property. If the property is a field, then the field name is returned. Otherwise, if the
+ * property is a method, then the name that is returned is the getter method name without the "get" or "is" prefix,
+ * and a lower case first letter.
+ *
+ * @return The name of the property
+ */
+ String getName();
+
+ /**
+ * Returns the property type
+ *
+ * @return The property type
+ */
+ Type getBaseType();
+
+ /**
+ * Returns the property type
+ *
+ * @return The property type
+ */
+ Class<V> getJavaClass();
+
+ /**
+ * Get the element responsible for retrieving the property value
+ *
+ * @return
+ */
+ AnnotatedElement getAnnotatedElement();
+
+ /**
+ * Get the member responsible for retrieving the property value
+ *
+ * @return
+ */
+ Member getMember();
+
+ /**
+ * Returns the property value for the specified bean. The property to be returned is either a field or getter
+ * method.
+ *
+ * @param bean The bean to read the property from
+ *
+ * @return The property value
+ *
+ * @throws ClassCastException if the value is not of the type V
+ */
+ V getValue(Object instance);
+
+ /**
+ * This method sets the property value for a specified bean to the specified value. The property to be set is either
+ * a field or setter method.
+ *
+ * @param bean The bean containing the property to set
+ * @param value The new property value
+ */
+ void setValue(Object instance, V value);
+
+ /**
+ * Returns the class that declares the property
+ *
+ * @return
+ */
+ Class<?> getDeclaringClass();
+
+ /**
+ * Indicates whether this is a read-only property
+ *
+ * @return
+ */
+ boolean isReadOnly();
+
+ /**
+ * Calls the setAccessible method on the underlying member(s).
+ * <p/>
+ * The operation should be performed within a {@link PrivilegedAction}
+ */
+ void setAccessible();
+
+ /**
+ * Indicates whether the given <code>annotation</code> is defined for this property. This method will consider
+ * the annotations present in both field and accessor method.
+ *
+ * @param annotation The Annotation to check.
+ *
+ * @return True if the annotation is defined. Otherwise is false.
+ */
+ boolean isAnnotationPresent(Class<? extends Annotation> annotation);
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java
new file mode 100644
index 0000000..2d2c41d
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java
@@ -0,0 +1,27 @@
+package org.keycloak.models.utils.reflection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * <p> A property criteria can be used to filter the properties found by a {@link PropertyQuery} </p> <p/> <p>
+ * DeltaSpike provides a number of property queries ( {@link TypedPropertyCriteria}, {@link NamedPropertyCriteria} and
+ * {@link AnnotatedPropertyCriteria}), or you can create a custom query by implementing this interface. </p>
+ *
+ * @see PropertyQuery#addCriteria(PropertyCriteria)
+ * @see PropertyQueries
+ * @see TypedPropertyCriteria
+ * @see AnnotatedPropertyCriteria
+ * @see NamedPropertyCriteria
+ */
+public interface PropertyCriteria {
+
+ /**
+ * Tests whether the specified method matches the criteria
+ *
+ * @param m
+ *
+ * @return true if the method matches
+ */
+ boolean methodMatches(Method m);
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java
new file mode 100644
index 0000000..d8cc8d0
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java
@@ -0,0 +1,25 @@
+package org.keycloak.models.utils.reflection;
+
+/**
+ * Utilities for working with property queries
+ *
+ * @see PropertyQuery
+ */
+public class PropertyQueries {
+
+ private PropertyQueries() {
+ }
+
+ /**
+ * Create a new {@link PropertyQuery}
+ *
+ * @param <V>
+ * @param targetClass
+ *
+ * @return
+ */
+ public static <V> PropertyQuery<V> createQuery(Class<?> targetClass) {
+ return new PropertyQuery<V>(targetClass);
+ }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java
new file mode 100644
index 0000000..5aa3833
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java
@@ -0,0 +1,162 @@
+package org.keycloak.models.utils.reflection;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p> Queries a target class for properties that match certain criteria. A property may either be a private or public
+ * field, declared by the target class or inherited from a superclass, or a public method declared by the target class
+ * or inherited from any of its superclasses. For properties that are exposed via a method, the property must be a
+ * JavaBean style property, i.e. it must provide both an accessor and mutator method according to the JavaBean
+ * specification. </p> <p/> <p> This class is not thread-safe, however the result returned by the getResultList() method
+ * is. </p>
+ *
+ * @see PropertyQueries
+ * @see PropertyCriteria
+ */
+public class PropertyQuery<V> {
+ private final Class<?> targetClass;
+ private final List<PropertyCriteria> criteria;
+
+ PropertyQuery(Class<?> targetClass) {
+ if (targetClass == null) {
+ throw new IllegalArgumentException("targetClass parameter may not be null");
+ }
+
+ this.targetClass = targetClass;
+ this.criteria = new ArrayList<PropertyCriteria>();
+ }
+
+ /**
+ * Add a criteria to query
+ *
+ * @param criteria the criteria to add
+ */
+ public PropertyQuery<V> addCriteria(PropertyCriteria criteria) {
+ this.criteria.add(criteria);
+ return this;
+ }
+
+ /**
+ * Get the first result from the query, causing the query to be run.
+ *
+ * @return the first result, or null if there are no results
+ */
+ public Property<V> getFirstResult() {
+ Map<String, Property<V>> results = getResultList();
+ return results.isEmpty() ? null : results.values().iterator().next();
+ }
+
+ /**
+ * Get the first result from the query that is not marked as read only, causing the query to be run.
+ *
+ * @return the first writable result, or null if there are no results
+ */
+ public Property<V> getFirstWritableResult() {
+ Map<String, Property<V>> results = getWritableResultList();
+ return results.isEmpty() ? null : results.values().iterator().next();
+ }
+
+ /**
+ * Get a single result from the query, causing the query to be run. An exception is thrown if the query does not
+ * return exactly one result.
+ *
+ * @return the single result
+ *
+ * @throws RuntimeException if the query does not return exactly one result
+ */
+ public Property<V> getSingleResult() {
+ Map<String, Property<V>> results = getResultList();
+ if (results.size() == 1) {
+ return results.values().iterator().next();
+ } else if (results.isEmpty()) {
+ throw new RuntimeException(
+ "Expected one property match, but the criteria did not match any properties on " +
+ targetClass.getName());
+ } else {
+ throw new RuntimeException("Expected one property match, but the criteria matched " + results.size() +
+ " properties on " + targetClass.getName());
+ }
+ }
+
+ /**
+ * Get a single result from the query that is not marked as read only, causing the query to be run. An exception is
+ * thrown if the query does not return exactly one result.
+ *
+ * @return the single writable result
+ *
+ * @throws RuntimeException if the query does not return exactly one result
+ */
+ public Property<V> getWritableSingleResult() {
+ Map<String, Property<V>> results = getWritableResultList();
+ if (results.size() == 1) {
+ return results.values().iterator().next();
+ } else if (results.isEmpty()) {
+ throw new RuntimeException(
+ "Expected one property match, but the criteria did not match any properties on " +
+ targetClass.getName());
+ } else {
+ throw new RuntimeException("Expected one property match, but the criteria matched " +
+ results.size() + " properties on " + targetClass.getName());
+ }
+ }
+
+ /**
+ * Get the result from the query, causing the query to be run.
+ *
+ * @return the results, or an empty list if there are no results
+ */
+ public Map<String, Property<V>> getResultList() {
+ return getResultList(false);
+ }
+
+ /**
+ * Get the non read only results from the query, causing the query to be run.
+ *
+ * @return the results, or an empty list if there are no results
+ */
+ public Map<String, Property<V>> getWritableResultList() {
+ return getResultList(true);
+ }
+
+ /**
+ * Get the result from the query, causing the query to be run.
+ *
+ * @param writable if this query should only return properties that are not read only
+ *
+ * @return the results, or an empty list if there are no results
+ */
+ private Map<String, Property<V>> getResultList(boolean writable) {
+ Map<String, Property<V>> properties = new HashMap<String, Property<V>>();
+
+ // First check public accessor methods (we ignore private methods)
+ for (Method method : targetClass.getMethods()) {
+ if (!(method.getName().startsWith("is") || method.getName().startsWith("get"))) {
+ continue;
+ }
+
+ boolean match = true;
+ for (PropertyCriteria c : criteria) {
+ if (!c.methodMatches(method)) {
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ MethodProperty<V> property = Properties.<V>createProperty(method);
+
+ if (!writable || !property.isReadOnly()) {
+ properties.put(property.getName(), property);
+ }
+ }
+ }
+
+ return Collections.unmodifiableMap(properties);
+ }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/Reflections.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/Reflections.java
new file mode 100644
index 0000000..e8491a4
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/Reflections.java
@@ -0,0 +1,980 @@
+package org.keycloak.models.utils.reflection;
+
+import java.beans.Introspector;
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.security.AccessController;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utility class for working with JDK Reflection and also CDI's {link Annotated} metadata.
+ */
+public class Reflections {
+ /**
+ * An empty array of type {@link java.lang.annotation.Annotation}, useful converting lists to arrays.
+ */
+ public static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
+
+ /**
+ * An empty array of type {@link Object}, useful for converting lists to arrays.
+ */
+ public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
+ public static final Type[] EMPTY_TYPES = {};
+
+ public static final Class<?>[] EMPTY_CLASSES = new Class<?>[0];
+
+ private Reflections() {
+ }
+
+ /**
+ * <p> Perform a runtime cast. Similar to {@link Class#cast(Object)}, but useful when you do not have a {@link
+ * Class} object for type you wish to cast to. </p> <p/> <p> {@link Class#cast(Object)} should be used if possible
+ * </p>
+ *
+ * @param <T> the type to cast to
+ * @param obj the object to perform the cast on
+ *
+ * @return the casted object
+ *
+ * @throws ClassCastException if the type T is not a subtype of the object
+ * @see Class#cast(Object)
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T cast(Object obj) {
+ return (T) obj;
+ }
+
+ /**
+ * Get all the declared fields on the class hierarchy. This <b>will</b> return overridden fields.
+ *
+ * @param clazz The class to search
+ *
+ * @return the set of all declared fields or an empty set if there are none
+ */
+ public static Set<Field> getAllDeclaredFields(Class<?> clazz) {
+ HashSet<Field> fields = new HashSet<Field>();
+ for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) {
+ for (Field a : c.getDeclaredFields()) {
+ fields.add(a);
+ }
+ }
+ return fields;
+ }
+
+ /**
+ * Search the class hierarchy for a field with the given name. Will return the nearest match, starting with the
+ * class specified and searching up the hierarchy.
+ *
+ * @param clazz The class to search
+ * @param name The name of the field to search for
+ *
+ * @return The field found, or null if no field is found
+ */
+ public static Field findDeclaredField(Class<?> clazz, String name) {
+ for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) {
+ try {
+ return c.getDeclaredField(name);
+ } catch (NoSuchFieldException e) {
+ // No-op, we continue looking up the class hierarchy
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Search for annotations with the specified meta annotation type
+ *
+ * @param annotations The annotation set to search
+ * @param metaAnnotationType The type of the meta annotation to search for
+ *
+ * @return The set of annotations with the specified meta annotation, or an empty set if none are found
+ */
+ public static Set<Annotation> getAnnotationsWithMetaAnnotation(
+ Set<Annotation> annotations, Class<? extends Annotation> metaAnnotationType) {
+ Set<Annotation> set = new HashSet<Annotation>();
+ for (Annotation annotation : annotations) {
+ if (annotation.annotationType().isAnnotationPresent(metaAnnotationType)) {
+ set.add(annotation);
+ }
+ }
+ return set;
+ }
+
+ /**
+ * Determine if a method exists in a specified class hierarchy
+ *
+ * @param clazz The class to search
+ * @param name The name of the method
+ *
+ * @return true if a method is found, otherwise false
+ */
+ public static boolean methodExists(Class<?> clazz, String name) {
+ for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) {
+ for (Method m : c.getDeclaredMethods()) {
+ if (m.getName().equals(name)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get all the declared methods on the class hierarchy. This <b>will</b> return overridden methods.
+ *
+ * @param clazz The class to search
+ *
+ * @return the set of all declared methods or an empty set if there are none
+ */
+ public static Set<Method> getAllDeclaredMethods(Class<?> clazz) {
+ HashSet<Method> methods = new HashSet<Method>();
+ for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) {
+ for (Method a : c.getDeclaredMethods()) {
+ methods.add(a);
+ }
+ }
+ return methods;
+ }
+
+ /**
+ * Search the class hierarchy for a method with the given name and arguments. Will return the nearest match,
+ * starting with the class specified and searching up the hierarchy.
+ *
+ * @param clazz The class to search
+ * @param name The name of the method to search for
+ * @param args The arguments of the method to search for
+ *
+ * @return The method found, or null if no method is found
+ */
+ public static Method findDeclaredMethod(Class<?> clazz, String name, Class<?>... args) {
+ for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) {
+ try {
+ return c.getDeclaredMethod(name, args);
+ } catch (NoSuchMethodException e) {
+ // No-op, continue the search
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Search the class hierarchy for a constructor with the given arguments. Will return the nearest match, starting
+ * with the class specified and searching up the hierarchy.
+ *
+ * @param clazz The class to search
+ * @param args The arguments of the constructor to search for
+ *
+ * @return The constructor found, or null if no constructor is found
+ */
+ public static Constructor<?> findDeclaredConstructor(Class<?> clazz, Class<?>... args) {
+ for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) {
+ try {
+ return c.getDeclaredConstructor(args);
+ } catch (NoSuchMethodException e) {
+ // No-op, continue the search
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get all the declared constructors on the class hierarchy. This <b>will</b> return overridden constructors.
+ *
+ * @param clazz The class to search
+ *
+ * @return the set of all declared constructors or an empty set if there are none
+ */
+ public static Set<Constructor<?>> getAllDeclaredConstructors(Class<?> clazz) {
+ HashSet<Constructor<?>> constructors = new HashSet<Constructor<?>>();
+ for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) {
+ for (Constructor<?> constructor : c.getDeclaredConstructors()) {
+ constructors.add(constructor);
+ }
+ }
+ return constructors;
+ }
+
+ /**
+ * Get the type of the member
+ *
+ * @param member The member
+ *
+ * @return The type of the member
+ *
+ * @throws UnsupportedOperationException if the member is not a field, method, or constructor
+ */
+ public static Class<?> getMemberType(Member member) {
+ if (member instanceof Field) {
+ return ((Field) member).getType();
+ } else if (member instanceof Method) {
+ return ((Method) member).getReturnType();
+ } else if (member instanceof Constructor<?>) {
+ return ((Constructor<?>) member).getDeclaringClass();
+ } else {
+ throw new UnsupportedOperationException("Cannot operate on a member of type " + member.getClass());
+ }
+ }
+
+ /**
+ * <p> Loads and initializes a class for the given name. </p> <p/> <p> If the Thread Context Class Loader is
+ * available, it will be used, otherwise the classloader used to load {@link Reflections} will be used </p> <p/> <p>
+ * It is also possible to specify additional classloaders to attempt to load the class with. If the first attempt
+ * fails, then these additional loaders are tried in order. </p>
+ *
+ * @param name the name of the class to load
+ * @param loaders additional classloaders to use to attempt to load the class
+ *
+ * @return the class object
+ *
+ * @throws ClassNotFoundException if the class cannot be found
+ */
+ public static <T> Class<T> classForName(String name, ClassLoader... loaders) throws ClassNotFoundException {
+ try {
+ if (Thread.currentThread().getContextClassLoader() != null) {
+ return (Class<T>) Class.forName(name, true, Thread.currentThread().getContextClassLoader());
+ } else {
+ return (Class<T>) Class.forName(name);
+ }
+ } catch (ClassNotFoundException e) {
+ for (ClassLoader l : loaders) {
+ try {
+ return (Class<T>) Class.forName(name, true, l);
+ } catch (ClassNotFoundException ex) {
+
+ }
+ }
+ }
+ if (Thread.currentThread().getContextClassLoader() != null) {
+ throw new ClassNotFoundException("Could not load class " + name +
+ " with the context class loader " + Thread.currentThread().getContextClassLoader().toString() +
+ " or any of the additional ClassLoaders: " + Arrays.toString(loaders));
+ } else {
+ throw new ClassNotFoundException("Could not load class " + name +
+ " using Class.forName or using any of the additional ClassLoaders: " +
+ Arrays.toString(loaders));
+ }
+ }
+
+ private static String buildInvokeMethodErrorMessage(Method method, Object obj, Object... args) {
+ StringBuilder message = new StringBuilder(
+ String.format("Exception invoking method [%s] on object [%s], using arguments [",
+ method.getName(), obj));
+ if (args != null) {
+ for (int i = 0; i < args.length; i++) {
+ message.append((i > 0 ? "," : "") + args[i]);
+ }
+ }
+ message.append("]");
+ return message.toString();
+ }
+
+ /**
+ * <p> Invoke the specified method on the provided instance, passing any additional arguments included in this
+ * method as arguments to the specified method. </p> <p/> <p>This method provides the same functionality and throws
+ * the same exceptions as {@link Reflections#invokeMethod(boolean, Method, Class, Object, Object...)}, with the
+ * expected return type set to {@link Object} and no change to the method's accessibility.</p>
+ *
+ * @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...)
+ * @see Method#invoke(Object, Object...)
+ */
+ public static Object invokeMethod(Method method, Object instance, Object... args) {
+ return invokeMethod(false, method, Object.class, instance, args);
+ }
+
+ /**
+ * <p> Invoke the specified method on the provided instance, passing any additional arguments included in this
+ * method as arguments to the specified method. </p> <p/> <p> This method attempts to set the accessible flag of the
+ * method in a {link PrivilegedAction} before invoking the method if the first argument is true. </p> <p/> <p>This
+ * method provides the same functionality and throws the same exceptions as {@link Reflections#invokeMethod(boolean,
+ * Method, Class, Object, Object...)}, with the expected return type set to {@link Object}.</p>
+ *
+ * @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...)
+ * @see Method#invoke(Object, Object...)
+ */
+ public static Object invokeMethod(boolean setAccessible, Method method, Object instance, Object... args) {
+ return invokeMethod(setAccessible, method, Object.class, instance, args);
+ }
+
+ /**
+ * <p> Invoke the specified method on the provided instance, passing any additional arguments included in this
+ * method as arguments to the specified method. </p> <p/> <p>This method provides the same functionality and throws
+ * the same exceptions as {@link Reflections#invokeMethod(boolean, Method, Class, Object, Object...)}, with the
+ * expected return type set to {@link Object} and honoring the accessibility of the method.</p>
+ *
+ * @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...)
+ * @see Method#invoke(Object, Object...)
+ */
+ public static <T> T invokeMethod(Method method, Class<T> expectedReturnType, Object instance, Object... args) {
+ return invokeMethod(false, method, expectedReturnType, instance, args);
+ }
+
+ /**
+ * <p> Invoke the method on the instance, with any arguments specified, casting the result of invoking the method to
+ * the expected return type. </p> <p/> <p> This method wraps {@link Method#invoke(Object, Object...)}, converting
+ * the checked exceptions that {@link Method#invoke(Object, Object...)} specifies to runtime exceptions. </p> <p/>
+ * <p> If instructed, this method attempts to set the accessible flag of the method in a {link PrivilegedAction}
+ * before invoking the method. </p>
+ *
+ * @param setAccessible flag indicating whether method should first be set as accessible
+ * @param method the method to invoke
+ * @param instance the instance to invoke the method
+ * @param args the arguments to the method
+ *
+ * @return the result of invoking the method, or null if the method's return type is void
+ *
+ * @throws RuntimeException if this <code>Method</code> object enforces Java language access control and the
+ * underlying method is inaccessible or if the underlying method throws an exception or if the initialization
+ * provoked by this method fails.
+ * @throws IllegalArgumentException if the method is an instance method and the specified <code>instance</code>
+ * argument is not an instance of the class or interface declaring the underlying method (or of a subclass or
+ * implementor thereof); if the number of actual and formal parameters differ; if an unwrapping conversion for
+ * primitive arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the
+ * corresponding formal parameter type by a method invocation conversion.
+ * @throws NullPointerException if the specified <code>instance</code> is null and the method is an instance
+ * method.
+ * @throws ClassCastException if the result of invoking the method cannot be cast to the expectedReturnType
+ * @throws ExceptionInInitializerError if the initialization provoked by this method fails.
+ * @see Method#invoke(Object, Object...)
+ */
+ public static <T> T invokeMethod(boolean setAccessible, Method method,
+ Class<T> expectedReturnType, Object instance, Object... args) {
+ if (setAccessible && !method.isAccessible()) {
+ setAccessible(method);
+ }
+
+ try {
+ return expectedReturnType.cast(method.invoke(instance, args));
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException(buildInvokeMethodErrorMessage(method, instance, args), ex);
+ } catch (IllegalArgumentException ex) {
+ throw new IllegalArgumentException(buildInvokeMethodErrorMessage(method, instance, args), ex);
+ } catch (InvocationTargetException ex) {
+ throw new RuntimeException(buildInvokeMethodErrorMessage(method, instance, args), ex.getCause());
+ } catch (NullPointerException ex) {
+ NullPointerException ex2 = new NullPointerException(buildInvokeMethodErrorMessage(method, instance, args));
+ ex2.initCause(ex.getCause());
+ throw ex2;
+ } catch (ExceptionInInitializerError e) {
+ ExceptionInInitializerError e2 = new ExceptionInInitializerError(
+ buildInvokeMethodErrorMessage(method, instance, args));
+ e2.initCause(e.getCause());
+ throw e2;
+ }
+ }
+
+ /**
+ * Set the accessibility flag on the {@link AccessibleObject} as described in {@link
+ * AccessibleObject#setAccessible(boolean)} within the context of a {link PrivilegedAction}.
+ *
+ * @param <A> member the accessible object type
+ * @param member the accessible object
+ *
+ * @return the accessible object after the accessible flag has been altered
+ */
+ public static <A extends AccessibleObject> A setAccessible(A member) {
+ AccessController.doPrivileged(new SetAccessiblePrivilegedAction(member));
+ return member;
+ }
+
+ private static String buildSetFieldValueErrorMessage(Field field, Object obj, Object value) {
+ return String.format("Exception setting [%s] field on object [%s] to value [%s]", field.getName(), obj, value);
+ }
+
+ private static String buildGetFieldValueErrorMessage(Field field, Object obj) {
+ return String.format("Exception reading [%s] field from object [%s].", field.getName(), obj);
+ }
+
+ public static Object getFieldValue(Field field, Object instance) {
+ return getFieldValue(field, instance, Object.class);
+ }
+
+ /**
+ * <p> Get the value of the field, on the specified instance, casting the value of the field to the expected type.
+ * </p> <p/> <p> This method wraps {@link Field#get(Object)}, converting the checked exceptions that {@link
+ * Field#get(Object)} specifies to runtime exceptions. </p>
+ *
+ * @param <T> the type of the field's value
+ * @param field the field to operate on
+ * @param instance the instance from which to retrieve the value
+ * @param expectedType the expected type of the field's value
+ *
+ * @return the value of the field
+ *
+ * @throws RuntimeException if the underlying field is inaccessible.
+ * @throws IllegalArgumentException if the specified <code>instance</code> is not an instance of the class or
+ * interface declaring the underlying field (or a subclass or implementor thereof).
+ * @throws NullPointerException if the specified <code>instance</code> is null and the field is an instance field.
+ * @throws ExceptionInInitializerError if the initialization provoked by this method fails.
+ */
+ public static <T> T getFieldValue(Field field, Object instance, Class<T> expectedType) {
+ try {
+ return Reflections.cast(field.get(instance));
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(buildGetFieldValueErrorMessage(field, instance), e);
+ } catch (NullPointerException ex) {
+ NullPointerException ex2 = new NullPointerException(buildGetFieldValueErrorMessage(field, instance));
+ ex2.initCause(ex.getCause());
+ throw ex2;
+ } catch (ExceptionInInitializerError e) {
+ ExceptionInInitializerError e2 = new ExceptionInInitializerError(
+ buildGetFieldValueErrorMessage(field, instance));
+ e2.initCause(e.getCause());
+ throw e2;
+ }
+ }
+
+ /**
+ * Extract the raw type, given a type.
+ *
+ * @param <T> the type
+ * @param type the type to extract the raw type from
+ *
+ * @return the raw type, or null if the raw type cannot be determined.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Class<T> getRawType(Type type) {
+ if (type instanceof Class<?>) {
+ return (Class<T>) type;
+ } else if (type instanceof ParameterizedType) {
+ if (((ParameterizedType) type).getRawType() instanceof Class<?>) {
+ return (Class<T>) ((ParameterizedType) type).getRawType();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Check if a class is serializable.
+ *
+ * @param clazz The class to check
+ *
+ * @return true if the class implements serializable or is a primitive
+ */
+ public static boolean isSerializable(Class<?> clazz) {
+ return clazz.isPrimitive() || Serializable.class.isAssignableFrom(clazz);
+ }
+
+
+ public static Map<Class<?>, Type> buildTypeMap(Set<Type> types) {
+ Map<Class<?>, Type> map = new HashMap<Class<?>, Type>();
+ for (Type type : types) {
+ if (type instanceof Class<?>) {
+ map.put((Class<?>) type, type);
+ } else if (type instanceof ParameterizedType) {
+ if (((ParameterizedType) type).getRawType() instanceof Class<?>) {
+ map.put((Class<?>) ((ParameterizedType) type).getRawType(), type);
+ }
+ } else if (type instanceof TypeVariable<?>) {
+
+ }
+ }
+ return map;
+ }
+
+ public static boolean isCacheable(Set<Annotation> annotations) {
+ for (Annotation qualifier : annotations) {
+ Class<?> clazz = qualifier.getClass();
+ if (clazz.isAnonymousClass() || (clazz.isMemberClass() && isStatic(clazz))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isCacheable(Annotation[] annotations) {
+ for (Annotation qualifier : annotations) {
+ Class<?> clazz = qualifier.getClass();
+ if (clazz.isAnonymousClass() || (clazz.isMemberClass() && isStatic(clazz))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Gets the property name from a getter method.
+ * <p/>
+ * We extend JavaBean conventions, allowing the getter method to have parameters
+ *
+ * @param method The getter method
+ *
+ * @return The name of the property. Returns null if method wasn't JavaBean getter-styled
+ */
+ public static String getPropertyName(Method method) {
+ String methodName = method.getName();
+ if (methodName.matches("^(get).*")) {
+ return Introspector.decapitalize(methodName.substring(3));
+ } else if (methodName.matches("^(is).*")) {
+ return Introspector.decapitalize(methodName.substring(2));
+ } else {
+ return null;
+ }
+
+ }
+
+ /**
+ * Checks if class is final
+ *
+ * @param clazz The class to check
+ *
+ * @return True if final, false otherwise
+ */
+ public static boolean isFinal(Class<?> clazz) {
+ return Modifier.isFinal(clazz.getModifiers());
+ }
+
+ public static int getNesting(Class<?> clazz) {
+ if (clazz.isMemberClass() && !isStatic(clazz)) {
+ return 1 + getNesting(clazz.getDeclaringClass());
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Checks if member is final
+ *
+ * @param member The member to check
+ *
+ * @return True if final, false otherwise
+ */
+ public static boolean isFinal(Member member) {
+ return Modifier.isFinal(member.getModifiers());
+ }
+
+ /**
+ * Checks if member is private
+ *
+ * @param member The member to check
+ *
+ * @return True if final, false otherwise
+ */
+ public static boolean isPrivate(Member member) {
+ return Modifier.isPrivate(member.getModifiers());
+ }
+
+ /**
+ * Checks if type or member is final
+ *
+ * @param type Type or member
+ *
+ * @return True if final, false otherwise
+ */
+ public static boolean isTypeOrAnyMethodFinal(Class<?> type) {
+ return getNonPrivateFinalMethodOrType(type) != null;
+ }
+
+ public static Object getNonPrivateFinalMethodOrType(Class<?> type) {
+ if (isFinal(type)) {
+ return type;
+ }
+ for (Method method : type.getDeclaredMethods()) {
+ if (isFinal(method) && !isPrivate(method)) {
+ return method;
+ }
+ }
+ return null;
+ }
+
+ public static boolean isPackagePrivate(int mod) {
+ return !(Modifier.isPrivate(mod) || Modifier.isProtected(mod) || Modifier.isPublic(mod));
+ }
+
+ /**
+ * Checks if type is static
+ *
+ * @param type Type to check
+ *
+ * @return True if static, false otherwise
+ */
+ public static boolean isStatic(Class<?> type) {
+ return Modifier.isStatic(type.getModifiers());
+ }
+
+ /**
+ * Checks if member is static
+ *
+ * @param member Member to check
+ *
+ * @return True if static, false otherwise
+ */
+ public static boolean isStatic(Member member) {
+ return Modifier.isStatic(member.getModifiers());
+ }
+
+ public static boolean isTransient(Member member) {
+ return Modifier.isTransient(member.getModifiers());
+ }
+
+ /**
+ * Checks if a method is abstract
+ *
+ * @param method
+ *
+ * @return
+ */
+ public static boolean isAbstract(Method method) {
+ return Modifier.isAbstract(method.getModifiers());
+ }
+
+ /**
+ * Checks if raw type is array type
+ *
+ * @param rawType The raw type to check
+ *
+ * @return True if array, false otherwise
+ */
+ public static boolean isArrayType(Class<?> rawType) {
+ return rawType.isArray();
+ }
+
+ /**
+ * Checks if type is parameterized type
+ *
+ * @param type The type to check
+ *
+ * @return True if parameterized, false otherwise
+ */
+ public static boolean isParameterizedType(Class<?> type) {
+ return type.getTypeParameters().length > 0;
+ }
+
+ public static boolean isParamerterizedTypeWithWildcard(Class<?> type) {
+ if (isParameterizedType(type)) {
+ return containsWildcards(type.getTypeParameters());
+ } else {
+ return false;
+ }
+ }
+
+ public static boolean containsWildcards(Type[] types) {
+ for (Type type : types) {
+ if (type instanceof WildcardType) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check the assignability of one type to another, taking into account the actual type arguements
+ *
+ * @param rawType1 the raw type of the class to check
+ * @param actualTypeArguments1 the actual type arguements to check, or an empty array if not a parameterized type
+ * @param rawType2 the raw type of the class to check
+ * @param actualTypeArguments2 the actual type arguements to check, or an empty array if not a parameterized type
+ *
+ * @return
+ */
+ public static boolean isAssignableFrom(Class<?> rawType1, Type[] actualTypeArguments1,
+ Class<?> rawType2, Type[] actualTypeArguments2) {
+ return Types.boxedClass(rawType1).isAssignableFrom(Types.boxedClass(rawType2)) &&
+ isAssignableFrom(actualTypeArguments1, actualTypeArguments2);
+ }
+
+ public static boolean matches(Class<?> rawType1, Type[] actualTypeArguments1,
+ Class<?> rawType2, Type[] actualTypeArguments2) {
+ return Types.boxedClass(rawType1).equals(Types.boxedClass(rawType2)) &&
+ isAssignableFrom(actualTypeArguments1, actualTypeArguments2);
+ }
+
+ public static boolean isAssignableFrom(Type[] actualTypeArguments1, Type[] actualTypeArguments2) {
+ for (int i = 0; i < actualTypeArguments1.length; i++) {
+ Type type1 = actualTypeArguments1[i];
+ Type type2 = Object.class;
+ if (actualTypeArguments2.length > i) {
+ type2 = actualTypeArguments2[i];
+ }
+ if (!isAssignableFrom(type1, type2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isAssignableFrom(Type type1, Set<? extends Type> types2) {
+ for (Type type2 : types2) {
+ if (isAssignableFrom(type1, type2)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean matches(Type type1, Set<? extends Type> types2) {
+ for (Type type2 : types2) {
+ if (matches(type1, type2)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isAssignableFrom(Type type1, Type[] types2) {
+ for (Type type2 : types2) {
+ if (isAssignableFrom(type1, type2)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isAssignableFrom(Type type1, Type type2) {
+ if (type1 instanceof Class<?>) {
+ Class<?> clazz = (Class<?>) type1;
+ if (isAssignableFrom(clazz, EMPTY_TYPES, type2)) {
+ return true;
+ }
+ }
+ if (type1 instanceof ParameterizedType) {
+ ParameterizedType parameterizedType1 = (ParameterizedType) type1;
+ if (parameterizedType1.getRawType() instanceof Class<?>) {
+ if (isAssignableFrom((Class<?>) parameterizedType1.getRawType(),
+ parameterizedType1.getActualTypeArguments(), type2)) {
+ return true;
+ }
+ }
+ }
+ if (type1 instanceof WildcardType) {
+ WildcardType wildcardType = (WildcardType) type1;
+ if (isTypeBounded(type2, wildcardType.getLowerBounds(), wildcardType.getUpperBounds())) {
+ return true;
+ }
+ }
+ if (type2 instanceof WildcardType) {
+ WildcardType wildcardType = (WildcardType) type2;
+ if (isTypeBounded(type1, wildcardType.getUpperBounds(), wildcardType.getLowerBounds())) {
+ return true;
+ }
+ }
+ if (type1 instanceof TypeVariable<?>) {
+ TypeVariable<?> typeVariable = (TypeVariable<?>) type1;
+ if (isTypeBounded(type2, EMPTY_TYPES, typeVariable.getBounds())) {
+ return true;
+ }
+ }
+ if (type2 instanceof TypeVariable<?>) {
+ TypeVariable<?> typeVariable = (TypeVariable<?>) type2;
+ if (isTypeBounded(type1, typeVariable.getBounds(), EMPTY_TYPES)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean matches(Type type1, Type type2) {
+ if (type1 instanceof Class<?>) {
+ Class<?> clazz = (Class<?>) type1;
+ if (matches(clazz, EMPTY_TYPES, type2)) {
+ return true;
+ }
+ }
+ if (type1 instanceof ParameterizedType) {
+ ParameterizedType parameterizedType1 = (ParameterizedType) type1;
+ if (parameterizedType1.getRawType() instanceof Class<?>) {
+ if (matches((Class<?>) parameterizedType1.getRawType(),
+ parameterizedType1.getActualTypeArguments(), type2)) {
+ return true;
+ }
+ }
+ }
+ if (type1 instanceof WildcardType) {
+ WildcardType wildcardType = (WildcardType) type1;
+ if (isTypeBounded(type2, wildcardType.getLowerBounds(), wildcardType.getUpperBounds())) {
+ return true;
+ }
+ }
+ if (type2 instanceof WildcardType) {
+ WildcardType wildcardType = (WildcardType) type2;
+ if (isTypeBounded(type1, wildcardType.getUpperBounds(), wildcardType.getLowerBounds())) {
+ return true;
+ }
+ }
+ if (type1 instanceof TypeVariable<?>) {
+ TypeVariable<?> typeVariable = (TypeVariable<?>) type1;
+ if (isTypeBounded(type2, EMPTY_TYPES, typeVariable.getBounds())) {
+ return true;
+ }
+ }
+ if (type2 instanceof TypeVariable<?>) {
+ TypeVariable<?> typeVariable = (TypeVariable<?>) type2;
+ if (isTypeBounded(type1, typeVariable.getBounds(), EMPTY_TYPES)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isTypeBounded(Type type, Type[] lowerBounds, Type[] upperBounds) {
+ if (lowerBounds.length > 0) {
+ if (!isAssignableFrom(type, lowerBounds)) {
+ return false;
+ }
+ }
+ if (upperBounds.length > 0) {
+ if (!isAssignableFrom(upperBounds, type)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isAssignableFrom(Class<?> rawType1, Type[] actualTypeArguments1, Type type2) {
+ if (type2 instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) type2;
+ if (parameterizedType.getRawType() instanceof Class<?>) {
+ if (isAssignableFrom(rawType1, actualTypeArguments1, (Class<?>) parameterizedType.getRawType(),
+ parameterizedType.getActualTypeArguments())) {
+ return true;
+ }
+ }
+ } else if (type2 instanceof Class<?>) {
+ Class<?> clazz = (Class<?>) type2;
+ if (isAssignableFrom(rawType1, actualTypeArguments1, clazz, EMPTY_TYPES)) {
+ return true;
+ }
+ } else if (type2 instanceof TypeVariable<?>) {
+ TypeVariable<?> typeVariable = (TypeVariable<?>) type2;
+ if (isTypeBounded(rawType1, actualTypeArguments1, typeVariable.getBounds())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean matches(Class<?> rawType1, Type[] actualTypeArguments1, Type type2) {
+ if (type2 instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) type2;
+ if (parameterizedType.getRawType() instanceof Class<?>) {
+ if (matches(rawType1, actualTypeArguments1, (Class<?>) parameterizedType.getRawType(),
+ parameterizedType.getActualTypeArguments())) {
+ return true;
+ }
+ }
+ } else if (type2 instanceof Class<?>) {
+ Class<?> clazz = (Class<?>) type2;
+ if (matches(rawType1, actualTypeArguments1, clazz, EMPTY_TYPES)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check the assiginability of a set of <b>flattened</b> types. This algorithm will check whether any of the types1
+ * matches a type in types2
+ *
+ * @param types1
+ * @param types2
+ *
+ * @return
+ */
+ public static boolean isAssignableFrom(Set<Type> types1, Set<Type> types2) {
+ for (Type type : types1) {
+ if (isAssignableFrom(type, types2)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether whether any of the types1 matches a type in types2
+ *
+ * @param types1
+ * @param types2
+ *
+ * @return
+ */
+ public static boolean matches(Set<Type> types1, Set<Type> types2) {
+ for (Type type : types1) {
+ if (matches(type, types2)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check the assignability of a set of <b>flattened</b> types. This algorithm will check whether any of the types1
+ * matches a type in types2
+ *
+ * @param types1
+ * @param type2
+ *
+ * @return
+ */
+ public static boolean isAssignableFrom(Set<Type> types1, Type type2) {
+ for (Type type : types1) {
+ if (isAssignableFrom(type, type2)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isAssignableFrom(Type[] types1, Type type2) {
+ for (Type type : types1) {
+ if (isAssignableFrom(type, type2)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isPrimitive(Type type) {
+ Class<?> rawType = getRawType(type);
+ return rawType == null ? false : rawType.isPrimitive();
+ }
+
+ /**
+ * <p>Creates a new instance of a class.</p>
+ *
+ * <p>This method will use the same class loader of the given class to create the new instance.</p>
+ *
+ * @param fromClass The class from where the instance should be created.
+ *
+ * @return A newly allocated instance of the class.
+ *
+ * @throws ClassNotFoundException
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ */
+ public static <T> T newInstance(final Class<T> fromClass) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
+ return newInstance(fromClass, fromClass.getName());
+ }
+
+ /**
+ * <p>Creates a new instance of a class given its <code>fullQualifiedName</code>.</p>
+ *
+ * <p>This method will use the same class loader of <code>type</code> to create the new instance.</p>
+ *
+ * @param type The class that will be used to get the class loader from.
+ * @param fullQualifiedName The full qualified name of the class from which the instance will be created.
+ *
+ * @return A newly allocated instance of the class.
+ *
+ * @throws ClassNotFoundException
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ */
+ public static <T> T newInstance(final Class<?> type, final String fullQualifiedName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
+ return (T) classForName(fullQualifiedName, type.getClassLoader()).newInstance();
+ }
+}
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/SetAccessiblePrivilegedAction.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/SetAccessiblePrivilegedAction.java
new file mode 100644
index 0000000..3d10120
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/SetAccessiblePrivilegedAction.java
@@ -0,0 +1,22 @@
+package org.keycloak.models.utils.reflection;
+
+import java.lang.reflect.AccessibleObject;
+import java.security.PrivilegedAction;
+
+/**
+ * A {@link java.security.PrivilegedAction} that calls {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)}
+ */
+public class SetAccessiblePrivilegedAction implements PrivilegedAction<Void> {
+
+ private final AccessibleObject member;
+
+ public SetAccessiblePrivilegedAction(AccessibleObject member) {
+ this.member = member;
+ }
+
+ public Void run() {
+ member.setAccessible(true);
+ return null;
+ }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/Types.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/Types.java
new file mode 100644
index 0000000..30015b4
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/Types.java
@@ -0,0 +1,54 @@
+package org.keycloak.models.utils.reflection;
+
+import java.lang.reflect.Type;
+
+/**
+ * Utility class for Types
+ */
+public class Types {
+ private Types() {
+
+ }
+
+ /**
+ * Gets the boxed type of a class
+ *
+ * @param type The type
+ *
+ * @return The boxed type
+ */
+ public static Type boxedType(Type type) {
+ if (type instanceof Class<?>) {
+ return boxedClass((Class<?>) type);
+ } else {
+ return type;
+ }
+ }
+
+ public static Class<?> boxedClass(Class<?> type) {
+ if (!type.isPrimitive()) {
+ return type;
+ } else if (type.equals(Boolean.TYPE)) {
+ return Boolean.class;
+ } else if (type.equals(Character.TYPE)) {
+ return Character.class;
+ } else if (type.equals(Byte.TYPE)) {
+ return Byte.class;
+ } else if (type.equals(Short.TYPE)) {
+ return Short.class;
+ } else if (type.equals(Integer.TYPE)) {
+ return Integer.class;
+ } else if (type.equals(Long.TYPE)) {
+ return Long.class;
+ } else if (type.equals(Float.TYPE)) {
+ return Float.class;
+ } else if (type.equals(Double.TYPE)) {
+ return Double.class;
+ } else if (type.equals(Void.TYPE)) {
+ return Void.class;
+ } else {
+ // Vagaries of if/else statement, can't be reached ;-)
+ return type;
+ }
+ }
+}
\ No newline at end of file
model/jpa/pom.xml 15(+15 -0)
diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml
index 3e4340a..15ac823 100755
--- a/model/jpa/pom.xml
+++ b/model/jpa/pom.xml
@@ -115,6 +115,21 @@
</configuration>
</plugin>
+ <!-- Test jar used in export-import -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>package-tests-jar</id>
+ <phase>package</phase>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
index 113fd0c..d23a0f4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
@@ -7,6 +7,7 @@ import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.jpa.entities.*;
+import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
@@ -99,7 +100,13 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
@Override
public RoleModel addRole(String name) {
+ return this.addRole(KeycloakModelUtils.generateId(), name);
+ }
+
+ @Override
+ public RoleModel addRole(String id, String name) {
ApplicationRoleEntity roleEntity = new ApplicationRoleEntity();
+ roleEntity.setId(id);
roleEntity.setName(name);
roleEntity.setApplication(applicationEntity);
em.persist(roleEntity);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index cd23f5f..96ac2c9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -29,8 +29,6 @@ import java.util.Set;
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"realm", "name"})})
public abstract class ClientEntity {
@Id
- @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
- @GeneratedValue(generator = "keycloak_generator")
private String id;
@Column(name = "name")
private String name;
@@ -63,6 +61,10 @@ public abstract class ClientEntity {
return id;
}
+ public void setId(String id) {
+ this.id = id;
+ }
+
public boolean isEnabled() {
return enabled;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
index 6101cae..3792995 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
@@ -28,8 +28,6 @@ import org.hibernate.annotations.GenericGenerator;
})
public abstract class RoleEntity {
@Id
- @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
- @GeneratedValue(generator = "keycloak_generator")
private String id;
private String description;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index f7ef212..f51ed94 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -44,8 +44,6 @@ import java.util.Set;
})
public class UserEntity {
@Id
- @GenericGenerator(name="uuid_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
- @GeneratedValue(generator = "uuid_generator")
protected String id;
protected String loginName;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UsernameLoginFailureEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UsernameLoginFailureEntity.java
index 6011ccc..e3336e0 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UsernameLoginFailureEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UsernameLoginFailureEntity.java
@@ -3,12 +3,17 @@ package org.keycloak.models.jpa.entities;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@Entity
+@NamedQueries({
+ @NamedQuery(name="getAllFailures", query="select failure from UsernameLoginFailureEntity failure"),
+})
public class UsernameLoginFailureEntity {
// we manually set the id to be username-realmid
// we may have a concurrent creation of the same login failure entry that we want to avoid
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 9bcca9f..d54cf3c 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
@@ -102,4 +102,13 @@ public class JpaKeycloakSession implements KeycloakSession {
if (em.getTransaction().isActive()) em.getTransaction().rollback();
if (em.isOpen()) em.close();
}
+
+ @Override
+ public void removeAllData() {
+ // Should be sufficient to delete all realms. Rest data should be removed in cascade
+ List<RealmModel> realms = getRealms();
+ for (RealmModel realm : realms) {
+ removeRealm(realm.getId());
+ }
+ }
}
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 a7c32ca..4b6758d 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
@@ -4,6 +4,7 @@ import org.keycloak.models.AuthenticationLinkModel;
import org.keycloak.models.AuthenticationProviderModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UsernameLoginFailureModel;
import org.keycloak.models.jpa.entities.ApplicationEntity;
import org.keycloak.models.jpa.entities.ApplicationRoleEntity;
@@ -435,6 +436,17 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public List<UsernameLoginFailureModel> getAllUserLoginFailures() {
+ TypedQuery<UsernameLoginFailureEntity> query = em.createNamedQuery("getAllFailures", UsernameLoginFailureEntity.class);
+ List<UsernameLoginFailureEntity> entities = query.getResultList();
+ List<UsernameLoginFailureModel> models = new ArrayList<UsernameLoginFailureModel>();
+ for (UsernameLoginFailureEntity entity : entities) {
+ models.add(new UsernameLoginFailureAdapter(entity));
+ }
+ return models;
+ }
+
+ @Override
public UserModel getUserByEmail(String email) {
TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserByEmail", UserEntity.class);
query.setParameter("email", email);
@@ -454,7 +466,13 @@ public class RealmAdapter implements RealmModel {
@Override
public UserModel addUser(String username) {
+ return this.addUser(KeycloakModelUtils.generateId(), username);
+ }
+
+ @Override
+ public UserModel addUser(String id, String username) {
UserEntity entity = new UserEntity();
+ entity.setId(id);
entity.setLoginName(username);
entity.setRealm(realm);
em.persist(entity);
@@ -580,7 +598,13 @@ public class RealmAdapter implements RealmModel {
@Override
public ApplicationModel addApplication(String name) {
+ return this.addApplication(KeycloakModelUtils.generateId(), name);
+ }
+
+ @Override
+ public ApplicationModel addApplication(String id, String name) {
ApplicationEntity applicationData = new ApplicationEntity();
+ applicationData.setId(id);
applicationData.setName(name);
applicationData.setEnabled(true);
applicationData.setRealm(realm);
@@ -805,7 +829,13 @@ public class RealmAdapter implements RealmModel {
@Override
public OAuthClientModel addOAuthClient(String name) {
+ return this.addOAuthClient(KeycloakModelUtils.generateId(), name);
+ }
+
+ @Override
+ public OAuthClientModel addOAuthClient(String id, String name) {
OAuthClientEntity data = new OAuthClientEntity();
+ data.setId(id);
data.setEnabled(true);
data.setName(name);
data.setRealm(realm);
@@ -949,7 +979,13 @@ public class RealmAdapter implements RealmModel {
@Override
public RoleModel addRole(String name) {
+ return this.addRole(KeycloakModelUtils.generateId(), name);
+ }
+
+ @Override
+ public RoleModel addRole(String id, String name) {
RealmRoleEntity entity = new RealmRoleEntity();
+ entity.setId(id);
entity.setName(name);
entity.setRealm(realm);
realm.getRoles().add(entity);
@@ -1170,13 +1206,9 @@ public class RealmAdapter implements RealmModel {
@Override
public void updateCredential(UserModel user, UserCredentialModel cred) {
- CredentialEntity credentialEntity = null;
UserEntity userEntity = ((UserAdapter) user).getUser();
- for (CredentialEntity entity : userEntity.getCredentials()) {
- if (entity.getType().equals(cred.getType())) {
- credentialEntity = entity;
- }
- }
+ CredentialEntity credentialEntity = getCredentialEntity(userEntity, cred.getType());
+
if (credentialEntity == null) {
credentialEntity = new CredentialEntity();
credentialEntity.setType(cred.getType());
@@ -1196,6 +1228,57 @@ public class RealmAdapter implements RealmModel {
em.flush();
}
+ private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) {
+ for (CredentialEntity entity : userEntity.getCredentials()) {
+ if (entity.getType().equals(credType)) {
+ return entity;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public List<UserCredentialValueModel> getCredentialsDirectly(UserModel user) {
+ UserEntity userEntity = ((UserAdapter) user).getUser();
+ List<CredentialEntity> credentials = new ArrayList<CredentialEntity>(userEntity.getCredentials());
+ List<UserCredentialValueModel> result = new ArrayList<UserCredentialValueModel>();
+
+ if (credentials != null) {
+ for (CredentialEntity credEntity : credentials) {
+ UserCredentialValueModel credModel = new UserCredentialValueModel();
+ credModel.setType(credEntity.getType());
+ credModel.setDevice(credEntity.getDevice());
+ credModel.setValue(credEntity.getValue());
+ credModel.setSalt(credEntity.getSalt());
+
+ result.add(credModel);
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public void updateCredentialDirectly(UserModel user, UserCredentialValueModel credModel) {
+ UserEntity userEntity = ((UserAdapter) user).getUser();
+ CredentialEntity credentialEntity = getCredentialEntity(userEntity, credModel.getType());
+
+ if (credentialEntity == null) {
+ credentialEntity = new CredentialEntity();
+ credentialEntity.setType(credModel.getType());
+ credentialEntity.setUser(userEntity);
+ em.persist(credentialEntity);
+ userEntity.getCredentials().add(credentialEntity);
+ }
+
+ credentialEntity.setValue(credModel.getValue());
+ credentialEntity.setSalt(credModel.getSalt());
+ credentialEntity.setDevice(credModel.getDevice());
+
+ em.flush();
+ }
+
@Override
public PasswordPolicy getPasswordPolicy() {
if (passwordPolicy == null) {
model/mongo/pom.xml 5(+0 -5)
diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml
index 6f8110d..01bce2f 100755
--- a/model/mongo/pom.xml
+++ b/model/mongo/pom.xml
@@ -62,11 +62,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>org.picketlink</groupId>
- <artifactId>picketlink-common</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<scope>provided</scope>
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
index f16400c..56951eb 100755
--- 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
@@ -40,4 +40,9 @@ public interface MongoStore {
<S> boolean pushItemToList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context);
<S> boolean pullItemFromList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPull, MongoStoreInvocationContext context);
+
+ /**
+ * Completely remove all data from DB
+ */
+ void removeAllEntities();
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/EntityInfo.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/EntityInfo.java
index b6ff046..8a68e96 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/EntityInfo.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/EntityInfo.java
@@ -1,12 +1,9 @@
package org.keycloak.models.mongo.impl;
import org.keycloak.models.mongo.api.MongoEntity;
-import org.picketlink.common.properties.Property;
+import org.keycloak.models.utils.reflection.Property;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
import java.util.Map;
/**
@@ -14,24 +11,19 @@ import java.util.Map;
*/
public class EntityInfo {
- private final Class<? extends MongoEntity> entityClass;
+ private final Class<?> entityClass;
private final String dbCollectionName;
private final Map<String, Property<Object>> properties;
- public EntityInfo(Class<? extends MongoEntity> entityClass, String dbCollectionName, List<Property<Object>> properties) {
+ public EntityInfo(Class<?> entityClass, String dbCollectionName, Map<String, Property<Object>> properties) {
this.entityClass = entityClass;
this.dbCollectionName = dbCollectionName;
-
- Map<String, Property<Object>> props= new HashMap<String, Property<Object>>();
- for (Property<Object> property : properties) {
- props.put(property.getName(), property);
- }
- this.properties = Collections.unmodifiableMap(props);
+ this.properties = properties;
}
- public Class<? extends MongoEntity> getEntityClass() {
+ public Class<?> getEntityClass() {
return entityClass;
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java
index e7468ad..79b0e95 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java
@@ -32,15 +32,16 @@ import org.keycloak.models.mongo.impl.types.MongoEntityMapper;
import org.keycloak.models.mongo.impl.types.SimpleMapper;
import org.keycloak.models.mongo.impl.types.StringToEnumMapper;
import org.keycloak.models.utils.KeycloakModelUtils;
-import org.picketlink.common.properties.Property;
-import org.picketlink.common.properties.query.AnnotatedPropertyCriteria;
-import org.picketlink.common.properties.query.PropertyQueries;
+import org.keycloak.models.utils.reflection.AnnotatedPropertyCriteria;
+import org.keycloak.models.utils.reflection.Property;
+import org.keycloak.models.utils.reflection.PropertyQueries;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -55,19 +56,19 @@ public class MongoStoreImpl implements MongoStore {
private static final Logger logger = Logger.getLogger(MongoStoreImpl.class);
private final MapperRegistry mapperRegistry;
- private ConcurrentMap<Class<? extends MongoEntity>, EntityInfo> entityInfoCache =
- new ConcurrentHashMap<Class<? extends MongoEntity>, EntityInfo>();
+ private ConcurrentMap<Class<?>, EntityInfo> entityInfoCache =
+ new ConcurrentHashMap<Class<?>, EntityInfo>();
- public MongoStoreImpl(DB database, boolean clearCollectionsOnStartup, Class<? extends MongoEntity>[] managedEntityTypes) {
+ public MongoStoreImpl(DB database, boolean clearCollectionsOnStartup, Class<?>[] managedEntityTypes) {
this.database = database;
mapperRegistry = new MapperRegistry();
- for (Class<?> simpleConverterClass : SIMPLE_TYPES) {
- SimpleMapper converter = new SimpleMapper(simpleConverterClass);
- mapperRegistry.addAppObjectMapper(converter);
- mapperRegistry.addDBObjectMapper(converter);
+ for (Class<?> simpleMapperClass : SIMPLE_TYPES) {
+ SimpleMapper mapper = new SimpleMapper(simpleMapperClass);
+ mapperRegistry.addAppObjectMapper(mapper);
+ mapperRegistry.addDBObjectMapper(mapper);
}
// Specific converter for ArrayList is added just for performance purposes to avoid recursive converter lookup (most of list idm will be ArrayList)
@@ -83,7 +84,7 @@ public class MongoStoreImpl implements MongoStore {
mapperRegistry.addAppObjectMapper(new EnumToStringMapper());
mapperRegistry.addDBObjectMapper(new StringToEnumMapper());
- for (Class<? extends MongoEntity> type : managedEntityTypes) {
+ for (Class<?> type : managedEntityTypes) {
getEntityInfo(type);
mapperRegistry.addAppObjectMapper(new MongoEntityMapper(this, mapperRegistry, type));
mapperRegistry.addDBObjectMapper(new BasicDBObjectMapper(this, mapperRegistry, type));
@@ -102,9 +103,9 @@ public class MongoStoreImpl implements MongoStore {
logger.info("Database " + this.database.getName() + " dropped in MongoDB");
}
- // Don't drop database, but just clear all data in managed collections (useful for development)
- protected void clearManagedCollections(Class<? extends MongoEntity>[] managedEntityTypes) {
- for (Class<? extends MongoEntity> clazz : managedEntityTypes) {
+ // Don't drop database, but just clear all data in managed collections (useful for export/import or during development)
+ protected void clearManagedCollections(Class<?>[] managedEntityTypes) {
+ for (Class<?> clazz : managedEntityTypes) {
DBCollection dbCollection = getDBCollectionForType(clazz);
if (dbCollection != null) {
dbCollection.remove(new BasicDBObject());
@@ -113,8 +114,8 @@ public class MongoStoreImpl implements MongoStore {
}
}
- protected void initManagedCollections(Class<? extends MongoEntity>[] managedEntityTypes) {
- for (Class<? extends MongoEntity> clazz : managedEntityTypes) {
+ protected void initManagedCollections(Class<?>[] managedEntityTypes) {
+ for (Class<?> clazz : managedEntityTypes) {
EntityInfo entityInfo = getEntityInfo(clazz);
String dbCollectionName = entityInfo.getDbCollectionName();
if (dbCollectionName != null && !database.collectionExists(dbCollectionName)) {
@@ -416,6 +417,13 @@ public class MongoStoreImpl implements MongoStore {
}
}
+ @Override
+ public void removeAllEntities() {
+ Set<Class<?>> managedTypes = this.entityInfoCache.keySet();
+ Class<? extends MongoEntity>[] arrayTemplate = (Class<? extends MongoEntity>[])new Class<?>[0];
+ this.clearManagedCollections(managedTypes.toArray(arrayTemplate));
+ }
+
// Possibility to add user-defined mappers
public void addAppObjectConverter(Mapper<?, ?> mapper) {
mapperRegistry.addAppObjectMapper(mapper);
@@ -425,10 +433,10 @@ public class MongoStoreImpl implements MongoStore {
mapperRegistry.addDBObjectMapper(mapper);
}
- public EntityInfo getEntityInfo(Class<? extends MongoEntity> entityClass) {
+ public EntityInfo getEntityInfo(Class<?> entityClass) {
EntityInfo entityInfo = entityInfoCache.get(entityClass);
if (entityInfo == null) {
- List<Property<Object>> properties = PropertyQueries.createQuery(entityClass).addCriteria(new AnnotatedPropertyCriteria(MongoField.class)).getResultList();
+ Map<String, Property<Object>> properties = PropertyQueries.createQuery(entityClass).getWritableResultList();
MongoCollection classAnnotation = entityClass.getAnnotation(MongoCollection.class);
@@ -473,7 +481,7 @@ public class MongoStoreImpl implements MongoStore {
return object;
}
- protected DBCollection getDBCollectionForType(Class<? extends MongoEntity> type) {
+ protected DBCollection getDBCollectionForType(Class<?> type) {
EntityInfo entityInfo = getEntityInfo(type);
String dbCollectionName = entityInfo.getDbCollectionName();
return dbCollectionName==null ? null : database.getCollection(dbCollectionName);
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectMapper.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectMapper.java
index 19cfa06..2d0adc0 100644
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectMapper.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectMapper.java
@@ -14,13 +14,13 @@ import org.keycloak.models.mongo.api.types.MapperContext;
import org.keycloak.models.mongo.api.types.MapperRegistry;
import org.keycloak.models.mongo.impl.MongoStoreImpl;
import org.keycloak.models.mongo.impl.EntityInfo;
-import org.picketlink.common.properties.Property;
-import org.picketlink.common.reflection.Types;
+import org.keycloak.models.utils.reflection.Property;
+import org.keycloak.models.utils.reflection.Types;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class BasicDBObjectMapper<S extends MongoEntity> implements Mapper<BasicDBObject, S> {
+public class BasicDBObjectMapper<S> implements Mapper<BasicDBObject, S> {
private static final Logger logger = Logger.getLogger(BasicDBObjectMapper.class);
@@ -73,7 +73,7 @@ public class BasicDBObjectMapper<S extends MongoEntity> implements Mapper<BasicD
return entity;
}
- private void setPropertyValue(MongoEntity entity, Object valueFromDB, Property property) {
+ private void setPropertyValue(Object entity, Object valueFromDB, Property property) {
if (valueFromDB == null) {
property.setValue(entity, null);
return;
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityMapper.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityMapper.java
index 1d3d665..b9f71ab 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityMapper.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityMapper.java
@@ -1,20 +1,19 @@
package org.keycloak.models.mongo.impl.types;
import com.mongodb.BasicDBObject;
-import org.keycloak.models.mongo.api.MongoEntity;
import org.keycloak.models.mongo.api.types.Mapper;
import org.keycloak.models.mongo.api.types.MapperContext;
import org.keycloak.models.mongo.api.types.MapperRegistry;
import org.keycloak.models.mongo.impl.MongoStoreImpl;
import org.keycloak.models.mongo.impl.EntityInfo;
-import org.picketlink.common.properties.Property;
+import org.keycloak.models.utils.reflection.Property;
import java.util.Collection;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class MongoEntityMapper<T extends MongoEntity> implements Mapper<T, BasicDBObject> {
+public class MongoEntityMapper<T> implements Mapper<T, BasicDBObject> {
private final MongoStoreImpl mongoStoreImpl;
private final MapperRegistry mapperRegistry;
@@ -37,11 +36,14 @@ public class MongoEntityMapper<T extends MongoEntity> implements Mapper<T, Basic
Collection<Property<Object>> props = entityInfo.getProperties();
for (Property<Object> property : props) {
String propName = property.getName();
- Object propValue = property.getValue(applicationObject);
- if (propValue != null) {
- Object dbValue = mapperRegistry.convertApplicationObjectToDBObject(propValue, Object.class);
- dbObject.put(propName, dbValue);
+ // Ignore "id" property
+ if (!"id".equals(propName)) {
+ Object propValue = property.getValue(applicationObject);
+ if (propValue != null) {
+ Object dbValue = propValue == null ? null : mapperRegistry.convertApplicationObjectToDBObject(propValue, Object.class);
+ dbObject.put(propName, dbValue);
+ }
}
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractMongoAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractMongoAdapter.java
index 6fcae4c..42b10cd 100644
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractMongoAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractMongoAdapter.java
@@ -1,13 +1,13 @@
package org.keycloak.models.mongo.keycloak.adapters;
-import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity;
+import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
import org.keycloak.models.mongo.api.MongoStore;
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public abstract class AbstractMongoAdapter<T extends AbstractMongoIdentifiableEntity> {
+public abstract class AbstractMongoAdapter<T extends MongoIdentifiableEntity> {
protected final MongoStoreInvocationContext invocationContext;
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
index 8369432..c30db32 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
@@ -8,8 +8,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
-import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
-import org.keycloak.models.mongo.keycloak.entities.RoleEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
import org.keycloak.models.mongo.utils.MongoModelUtils;
import java.util.ArrayList;
@@ -20,9 +20,9 @@ import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class ApplicationAdapter extends ClientAdapter<ApplicationEntity> implements ApplicationModel {
+public class ApplicationAdapter extends ClientAdapter<MongoApplicationEntity> implements ApplicationModel {
- public ApplicationAdapter(RealmModel realm, ApplicationEntity applicationEntity, MongoStoreInvocationContext invContext) {
+ public ApplicationAdapter(RealmModel realm, MongoApplicationEntity applicationEntity, MongoStoreInvocationContext invContext) {
super(realm, applicationEntity, invContext);
}
@@ -103,7 +103,7 @@ public class ApplicationAdapter extends ClientAdapter<ApplicationEntity> impleme
.and("name").is(name)
.and("applicationId").is(getId())
.get();
- RoleEntity role = getMongoStore().loadSingleEntity(RoleEntity.class, query, invocationContext);
+ MongoRoleEntity role = getMongoStore().loadSingleEntity(MongoRoleEntity.class, query, invocationContext);
if (role == null) {
return null;
} else {
@@ -113,7 +113,13 @@ public class ApplicationAdapter extends ClientAdapter<ApplicationEntity> impleme
@Override
public RoleAdapter addRole(String name) {
- RoleEntity roleEntity = new RoleEntity();
+ return this.addRole(null, name);
+ }
+
+ @Override
+ public RoleAdapter addRole(String id, String name) {
+ MongoRoleEntity roleEntity = new MongoRoleEntity();
+ roleEntity.setId(id);
roleEntity.setName(name);
roleEntity.setApplicationId(getId());
@@ -124,7 +130,7 @@ public class ApplicationAdapter extends ClientAdapter<ApplicationEntity> impleme
@Override
public boolean removeRole(RoleModel role) {
- return getMongoStore().removeEntity(RoleEntity.class, role.getId(), invocationContext);
+ return getMongoStore().removeEntity(MongoRoleEntity.class, role.getId(), invocationContext);
}
@Override
@@ -132,10 +138,10 @@ public class ApplicationAdapter extends ClientAdapter<ApplicationEntity> impleme
DBObject query = new QueryBuilder()
.and("applicationId").is(getId())
.get();
- List<RoleEntity> roles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext);
+ List<MongoRoleEntity> roles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext);
Set<RoleModel> result = new HashSet<RoleModel>();
- for (RoleEntity role : roles) {
+ for (MongoRoleEntity role : roles) {
result.add(new RoleAdapter(getRealm(), role, this, invocationContext));
}
@@ -145,9 +151,9 @@ public class ApplicationAdapter extends ClientAdapter<ApplicationEntity> impleme
@Override
public Set<RoleModel> getApplicationRoleMappings(UserModel user) {
Set<RoleModel> result = new HashSet<RoleModel>();
- List<RoleEntity> roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext);
+ List<MongoRoleEntity> roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext);
- for (RoleEntity role : roles) {
+ for (MongoRoleEntity role : roles) {
if (getId().equals(role.getApplicationId())) {
result.add(new RoleAdapter(getRealm(), role, this, invocationContext));
}
@@ -158,9 +164,9 @@ public class ApplicationAdapter extends ClientAdapter<ApplicationEntity> impleme
@Override
public Set<RoleModel> getApplicationScopeMappings(ClientModel client) {
Set<RoleModel> result = new HashSet<RoleModel>();
- List<RoleEntity> roles = MongoModelUtils.getAllScopesOfClient(client, invocationContext);
+ List<MongoRoleEntity> roles = MongoModelUtils.getAllScopesOfClient(client, invocationContext);
- for (RoleEntity role : roles) {
+ for (MongoRoleEntity role : roles) {
if (getId().equals(role.getApplicationId())) {
result.add(new RoleAdapter(getRealm(), role, this, invocationContext));
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index 181b812..f651f3a 100644
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -7,13 +7,14 @@ import java.util.Set;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.entities.ClientEntity;
+import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
-import org.keycloak.models.mongo.keycloak.entities.ClientEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class ClientAdapter<T extends ClientEntity> extends AbstractMongoAdapter<T> implements ClientModel {
+public class ClientAdapter<T extends MongoIdentifiableEntity> extends AbstractMongoAdapter<T> implements ClientModel {
protected final T clientEntity;
private final RealmModel realm;
@@ -29,6 +30,11 @@ public class ClientAdapter<T extends ClientEntity> extends AbstractMongoAdapter<
return clientEntity;
}
+ // ClientEntity doesn't extend MongoIdentifiableEntity
+ public ClientEntity getMongoEntityAsClient() {
+ return (ClientEntity)getMongoEntity();
+ }
+
@Override
public String getId() {
return getMongoEntity().getId();
@@ -36,25 +42,25 @@ public class ClientAdapter<T extends ClientEntity> extends AbstractMongoAdapter<
@Override
public String getClientId() {
- return getMongoEntity().getName();
+ return getMongoEntityAsClient().getName();
}
@Override
public long getAllowedClaimsMask() {
- return getMongoEntity().getAllowedClaimsMask();
+ return getMongoEntityAsClient().getAllowedClaimsMask();
}
@Override
public void setAllowedClaimsMask(long mask) {
- getMongoEntity().setAllowedClaimsMask(mask);
+ getMongoEntityAsClient().setAllowedClaimsMask(mask);
updateMongoEntity();
}
@Override
public Set<String> getWebOrigins() {
Set<String> result = new HashSet<String>();
- if (getMongoEntity().getWebOrigins() != null) {
- result.addAll(clientEntity.getWebOrigins());
+ if (getMongoEntityAsClient().getWebOrigins() != null) {
+ result.addAll(getMongoEntityAsClient().getWebOrigins());
}
return result;
}
@@ -63,7 +69,7 @@ public class ClientAdapter<T extends ClientEntity> extends AbstractMongoAdapter<
public void setWebOrigins(Set<String> webOrigins) {
List<String> result = new ArrayList<String>();
result.addAll(webOrigins);
- clientEntity.setWebOrigins(result);
+ getMongoEntityAsClient().setWebOrigins(result);
updateMongoEntity();
}
@@ -80,8 +86,8 @@ public class ClientAdapter<T extends ClientEntity> extends AbstractMongoAdapter<
@Override
public Set<String> getRedirectUris() {
Set<String> result = new HashSet<String>();
- if (clientEntity.getRedirectUris() != null) {
- result.addAll(clientEntity.getRedirectUris());
+ if (getMongoEntityAsClient().getRedirectUris() != null) {
+ result.addAll(getMongoEntityAsClient().getRedirectUris());
}
return result;
}
@@ -90,7 +96,7 @@ public class ClientAdapter<T extends ClientEntity> extends AbstractMongoAdapter<
public void setRedirectUris(Set<String> redirectUris) {
List<String> result = new ArrayList<String>();
result.addAll(redirectUris);
- clientEntity.setRedirectUris(result);
+ getMongoEntityAsClient().setRedirectUris(result);
updateMongoEntity();
}
@@ -106,39 +112,39 @@ public class ClientAdapter<T extends ClientEntity> extends AbstractMongoAdapter<
@Override
public boolean isEnabled() {
- return clientEntity.isEnabled();
+ return getMongoEntityAsClient().isEnabled();
}
@Override
public void setEnabled(boolean enabled) {
- clientEntity.setEnabled(enabled);
+ getMongoEntityAsClient().setEnabled(enabled);
updateMongoEntity();
}
@Override
public boolean validateSecret(String secret) {
- return secret.equals(clientEntity.getSecret());
+ return secret.equals(getMongoEntityAsClient().getSecret());
}
@Override
public String getSecret() {
- return clientEntity.getSecret();
+ return getMongoEntityAsClient().getSecret();
}
@Override
public void setSecret(String secret) {
- clientEntity.setSecret(secret);
+ getMongoEntityAsClient().setSecret(secret);
updateMongoEntity();
}
@Override
public boolean isPublicClient() {
- return clientEntity.isPublicClient();
+ return getMongoEntityAsClient().isPublicClient();
}
@Override
public void setPublicClient(boolean flag) {
- clientEntity.setPublicClient(flag);
+ getMongoEntityAsClient().setPublicClient(flag);
updateMongoEntity();
}
@@ -149,12 +155,12 @@ public class ClientAdapter<T extends ClientEntity> extends AbstractMongoAdapter<
@Override
public int getNotBefore() {
- return clientEntity.getNotBefore();
+ return getMongoEntityAsClient().getNotBefore();
}
@Override
public void setNotBefore(int notBefore) {
- clientEntity.setNotBefore(notBefore);
+ getMongoEntityAsClient().setNotBefore(notBefore);
updateMongoEntity();
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java
index e8baa31..0c13eb9 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java
@@ -9,7 +9,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.mongo.api.MongoStore;
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.mongo.impl.context.TransactionMongoStoreInvocationContext;
-import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.ArrayList;
@@ -40,13 +40,18 @@ public class MongoKeycloakSession implements KeycloakSession {
}
@Override
+ public void removeAllData() {
+ getMongoStore().removeAllEntities();
+ }
+
+ @Override
public RealmModel createRealm(String name) {
return createRealm(KeycloakModelUtils.generateId(), name);
}
@Override
public RealmModel createRealm(String id, String name) {
- RealmEntity newRealm = new RealmEntity();
+ MongoRealmEntity newRealm = new MongoRealmEntity();
newRealm.setId(id);
newRealm.setName(name);
@@ -57,17 +62,17 @@ public class MongoKeycloakSession implements KeycloakSession {
@Override
public RealmModel getRealm(String id) {
- RealmEntity realmEntity = getMongoStore().loadEntity(RealmEntity.class, id, invocationContext);
+ MongoRealmEntity realmEntity = getMongoStore().loadEntity(MongoRealmEntity.class, id, invocationContext);
return realmEntity != null ? new RealmAdapter(realmEntity, invocationContext) : null;
}
@Override
public List<RealmModel> getRealms() {
DBObject query = new BasicDBObject();
- List<RealmEntity> realms = getMongoStore().loadEntities(RealmEntity.class, query, invocationContext);
+ List<MongoRealmEntity> realms = getMongoStore().loadEntities(MongoRealmEntity.class, query, invocationContext);
List<RealmModel> results = new ArrayList<RealmModel>();
- for (RealmEntity realmEntity : realms) {
+ for (MongoRealmEntity realmEntity : realms) {
results.add(new RealmAdapter(realmEntity, invocationContext));
}
return results;
@@ -78,7 +83,7 @@ public class MongoKeycloakSession implements KeycloakSession {
DBObject query = new QueryBuilder()
.and("name").is(name)
.get();
- RealmEntity realm = getMongoStore().loadSingleEntity(RealmEntity.class, query, invocationContext);
+ MongoRealmEntity realm = getMongoStore().loadSingleEntity(MongoRealmEntity.class, query, invocationContext);
if (realm == null) return null;
return new RealmAdapter(realm, invocationContext);
@@ -86,7 +91,7 @@ public class MongoKeycloakSession implements KeycloakSession {
@Override
public boolean removeRealm(String id) {
- return getMongoStore().removeEntity(RealmEntity.class, id, invocationContext);
+ return getMongoStore().removeEntity(MongoRealmEntity.class, id, invocationContext);
}
protected MongoStore getMongoStore() {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java
index dcd787d..d431803 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java
@@ -3,20 +3,20 @@ package org.keycloak.models.mongo.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.models.mongo.api.MongoEntity;
+import org.keycloak.models.entities.AuthenticationLinkEntity;
+import org.keycloak.models.entities.AuthenticationProviderEntity;
+import org.keycloak.models.entities.CredentialEntity;
+import org.keycloak.models.entities.RequiredCredentialEntity;
+import org.keycloak.models.entities.SocialLinkEntity;
import org.keycloak.models.mongo.api.MongoStore;
import org.keycloak.models.mongo.impl.MongoStoreImpl;
import org.keycloak.models.mongo.keycloak.config.MongoClientProvider;
-import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
-import org.keycloak.models.mongo.keycloak.entities.AuthenticationLinkEntity;
-import org.keycloak.models.mongo.keycloak.entities.AuthenticationProviderEntity;
-import org.keycloak.models.mongo.keycloak.entities.CredentialEntity;
-import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity;
-import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
-import org.keycloak.models.mongo.keycloak.entities.RequiredCredentialEntity;
-import org.keycloak.models.mongo.keycloak.entities.RoleEntity;
-import org.keycloak.models.mongo.keycloak.entities.SocialLinkEntity;
-import org.keycloak.models.mongo.keycloak.entities.UserEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoUsernameLoginFailureEntity;
/**
* KeycloakSessionFactory implementation based on MongoDB
@@ -26,17 +26,18 @@ import org.keycloak.models.mongo.keycloak.entities.UserEntity;
public class MongoKeycloakSessionFactory implements KeycloakSessionFactory {
protected static final Logger logger = Logger.getLogger(MongoKeycloakSessionFactory.class);
- private static final Class<? extends MongoEntity>[] MANAGED_ENTITY_TYPES = (Class<? extends MongoEntity>[])new Class<?>[] {
- RealmEntity.class,
- UserEntity.class,
- RoleEntity.class,
+ private static final Class<?>[] MANAGED_ENTITY_TYPES = (Class<?>[])new Class<?>[] {
+ MongoRealmEntity.class,
+ MongoUserEntity.class,
+ MongoRoleEntity.class,
RequiredCredentialEntity.class,
AuthenticationProviderEntity.class,
CredentialEntity.class,
SocialLinkEntity.class,
AuthenticationLinkEntity.class,
- ApplicationEntity.class,
- OAuthClientEntity.class
+ MongoApplicationEntity.class,
+ MongoOAuthClientEntity.class,
+ MongoUsernameLoginFailureEntity.class
};
private final MongoClientProvider mongoClientProvider;
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java
index b9b5e04..fbb2b3e 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java
@@ -3,14 +3,14 @@ package org.keycloak.models.mongo.keycloak.adapters;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
-import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class OAuthClientAdapter extends ClientAdapter<OAuthClientEntity> implements OAuthClientModel {
+public class OAuthClientAdapter extends ClientAdapter<MongoOAuthClientEntity> implements OAuthClientModel {
- public OAuthClientAdapter(RealmModel realm, OAuthClientEntity oauthClientEntity, MongoStoreInvocationContext invContext) {
+ public OAuthClientAdapter(RealmModel realm, MongoOAuthClientEntity oauthClientEntity, MongoStoreInvocationContext invContext) {
super(realm, oauthClientEntity, invContext);
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 10793bd..52308de 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -14,19 +14,21 @@ import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.entities.AuthenticationLinkEntity;
+import org.keycloak.models.entities.AuthenticationProviderEntity;
+import org.keycloak.models.entities.CredentialEntity;
+import org.keycloak.models.entities.RequiredCredentialEntity;
+import org.keycloak.models.entities.SocialLinkEntity;
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
-import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
-import org.keycloak.models.mongo.keycloak.entities.AuthenticationLinkEntity;
-import org.keycloak.models.mongo.keycloak.entities.AuthenticationProviderEntity;
-import org.keycloak.models.mongo.keycloak.entities.CredentialEntity;
-import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity;
-import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
-import org.keycloak.models.mongo.keycloak.entities.RequiredCredentialEntity;
-import org.keycloak.models.mongo.keycloak.entities.RoleEntity;
-import org.keycloak.models.mongo.keycloak.entities.SocialLinkEntity;
-import org.keycloak.models.mongo.keycloak.entities.UserEntity;
-import org.keycloak.models.mongo.keycloak.entities.UsernameLoginFailureEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoUsernameLoginFailureEntity;
import org.keycloak.models.mongo.utils.MongoModelUtils;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
@@ -39,7 +41,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -48,18 +49,18 @@ import java.util.regex.Pattern;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements RealmModel {
+public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> implements RealmModel {
private static final Logger logger = Logger.getLogger(RealmAdapter.class);
- private final RealmEntity realm;
+ private final MongoRealmEntity realm;
protected volatile transient PublicKey publicKey;
protected volatile transient PrivateKey privateKey;
private volatile transient PasswordPolicy passwordPolicy;
- public RealmAdapter(RealmEntity realmEntity, MongoStoreInvocationContext invocationContext) {
+ public RealmAdapter(MongoRealmEntity realmEntity, MongoStoreInvocationContext invocationContext) {
super(invocationContext);
this.realm = realmEntity;
}
@@ -132,6 +133,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setBruteForceProtected(boolean value) {
realm.setBruteForceProtected(value);
+ updateRealm();
}
@Override
@@ -142,6 +144,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setMaxFailureWaitSeconds(int val) {
realm.setMaxFailureWaitSeconds(val);
+ updateRealm();
}
@Override
@@ -152,6 +155,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setWaitIncrementSeconds(int val) {
realm.setWaitIncrementSeconds(val);
+ updateRealm();
}
@Override
@@ -162,6 +166,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setQuickLoginCheckMilliSeconds(long val) {
realm.setQuickLoginCheckMilliSeconds(val);
+ updateRealm();
}
@Override
@@ -172,6 +177,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setMinimumQuickLoginWaitSeconds(int val) {
realm.setMinimumQuickLoginWaitSeconds(val);
+ updateRealm();
}
@@ -183,6 +189,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setMaxDeltaTimeSeconds(int val) {
realm.setMaxDeltaTimeSeconds(val);
+ updateRealm();
}
@Override
@@ -193,6 +200,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setFailureFactor(int failureFactor) {
realm.setFailureFactor(failureFactor);
+ updateRealm();
}
@@ -403,7 +411,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
.and("loginName").is(name)
.and("realmId").is(getId())
.get();
- UserEntity user = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext);
+ MongoUserEntity user = getMongoStore().loadSingleEntity(MongoUserEntity.class, query, invocationContext);
if (user == null) {
return null;
@@ -418,7 +426,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
.and("username").is(name)
.and("realmId").is(getId())
.get();
- UsernameLoginFailureEntity user = getMongoStore().loadSingleEntity(UsernameLoginFailureEntity.class, query, invocationContext);
+ MongoUsernameLoginFailureEntity user = getMongoStore().loadSingleEntity(MongoUsernameLoginFailureEntity.class, query, invocationContext);
if (user == null) {
return null;
@@ -434,10 +442,8 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
return userLoginFailure;
}
- UsernameLoginFailureEntity userEntity = new UsernameLoginFailureEntity();
+ MongoUsernameLoginFailureEntity userEntity = new MongoUsernameLoginFailureEntity();
userEntity.setUsername(username);
- // Compatibility with JPA model, which has user disabled by default
- // userEntity.setEnabled(true);
userEntity.setRealmId(getId());
getMongoStore().insertEntity(userEntity, invocationContext);
@@ -445,12 +451,29 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
}
@Override
+ public List<UsernameLoginFailureModel> getAllUserLoginFailures() {
+ DBObject query = new QueryBuilder()
+ .and("realmId").is(getId())
+ .get();
+ List<MongoUsernameLoginFailureEntity> failures = getMongoStore().loadEntities(MongoUsernameLoginFailureEntity.class, query, invocationContext);
+
+ List<UsernameLoginFailureModel> result = new ArrayList<UsernameLoginFailureModel>();
+
+ if (failures == null) return result;
+ for (MongoUsernameLoginFailureEntity failure : failures) {
+ result.add(new UsernameLoginFailureAdapter(invocationContext, failure));
+ }
+
+ return result;
+ }
+
+ @Override
public UserModel getUserByEmail(String email) {
DBObject query = new QueryBuilder()
.and("email").is(email)
.and("realmId").is(getId())
.get();
- UserEntity user = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext);
+ MongoUserEntity user = getMongoStore().loadSingleEntity(MongoUserEntity.class, query, invocationContext);
if (user == null) {
return null;
@@ -461,7 +484,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public UserModel getUserById(String id) {
- UserEntity user = getMongoStore().loadEntity(UserEntity.class, id, invocationContext);
+ MongoUserEntity user = getMongoStore().loadEntity(MongoUserEntity.class, id, invocationContext);
// Check that it's user from this realm
if (user == null || !getId().equals(user.getRealmId())) {
@@ -473,7 +496,12 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public UserAdapter addUser(String username) {
- UserAdapter userModel = addUserEntity(username);
+ return this.addUser(null, username);
+ }
+
+ @Override
+ public UserAdapter addUser(String id, String username) {
+ UserAdapter userModel = addUserEntity(id, username);
for (String r : getDefaultRoles()) {
grantRole(userModel, getRole(r));
@@ -488,9 +516,9 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
return userModel;
}
- // Add just user entity without defaultRoles
- protected UserAdapter addUserEntity(String username) {
- UserEntity userEntity = new UserEntity();
+ protected UserAdapter addUserEntity(String id, String username) {
+ MongoUserEntity userEntity = new MongoUserEntity();
+ userEntity.setId(id);
userEntity.setLoginName(username);
// Compatibility with JPA model, which has user disabled by default
// userEntity.setEnabled(true);
@@ -506,7 +534,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
.and("loginName").is(name)
.and("realmId").is(getId())
.get();
- return getMongoStore().removeEntities(UserEntity.class, query, invocationContext);
+ return getMongoStore().removeEntities(MongoUserEntity.class, query, invocationContext);
}
@Override
@@ -515,7 +543,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
.and("name").is(name)
.and("realmId").is(getId())
.get();
- RoleEntity role = getMongoStore().loadSingleEntity(RoleEntity.class, query, invocationContext);
+ MongoRoleEntity role = getMongoStore().loadSingleEntity(MongoRoleEntity.class, query, invocationContext);
if (role == null) {
return null;
} else {
@@ -525,7 +553,13 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public RoleModel addRole(String name) {
- RoleEntity roleEntity = new RoleEntity();
+ return this.addRole(null, name);
+ }
+
+ @Override
+ public RoleModel addRole(String id, String name) {
+ MongoRoleEntity roleEntity = new MongoRoleEntity();
+ roleEntity.setId(id);
roleEntity.setName(name);
roleEntity.setRealmId(getId());
@@ -541,7 +575,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public boolean removeRoleById(String id) {
- return getMongoStore().removeEntity(RoleEntity.class, id, invocationContext);
+ return getMongoStore().removeEntity(MongoRoleEntity.class, id, invocationContext);
}
@Override
@@ -549,12 +583,12 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
- List<RoleEntity> roles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext);
+ List<MongoRoleEntity> roles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext);
Set<RoleModel> result = new HashSet<RoleModel>();
if (roles == null) return result;
- for (RoleEntity role : roles) {
+ for (MongoRoleEntity role : roles) {
result.add(new RoleAdapter(this, role, this, invocationContext));
}
@@ -563,7 +597,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public RoleModel getRoleById(String id) {
- RoleEntity role = getMongoStore().loadEntity(RoleEntity.class, id, invocationContext);
+ MongoRoleEntity role = getMongoStore().loadEntity(MongoRoleEntity.class, id, invocationContext);
if (role == null) return null;
if (role.getRealmId() != null) {
if (!role.getRealmId().equals(this.getId())) return null;
@@ -615,7 +649,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public ApplicationModel getApplicationById(String id) {
- ApplicationEntity appData = getMongoStore().loadEntity(ApplicationEntity.class, id, invocationContext);
+ MongoApplicationEntity appData = getMongoStore().loadEntity(MongoApplicationEntity.class, id, invocationContext);
// Check if application belongs to this realm
if (appData == null || !getId().equals(appData.getRealmId())) {
@@ -631,7 +665,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
.and("realmId").is(getId())
.and("name").is(name)
.get();
- ApplicationEntity appEntity = getMongoStore().loadSingleEntity(ApplicationEntity.class, query, invocationContext);
+ MongoApplicationEntity appEntity = getMongoStore().loadSingleEntity(MongoApplicationEntity.class, query, invocationContext);
return appEntity == null ? null : new ApplicationAdapter(this, appEntity, invocationContext);
}
@@ -649,10 +683,10 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
- List<ApplicationEntity> appDatas = getMongoStore().loadEntities(ApplicationEntity.class, query, invocationContext);
+ List<MongoApplicationEntity> appDatas = getMongoStore().loadEntities(MongoApplicationEntity.class, query, invocationContext);
List<ApplicationModel> result = new ArrayList<ApplicationModel>();
- for (ApplicationEntity appData : appDatas) {
+ for (MongoApplicationEntity appData : appDatas) {
result.add(new ApplicationAdapter(this, appData, invocationContext));
}
return result;
@@ -660,7 +694,13 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public ApplicationModel addApplication(String name) {
- ApplicationEntity appData = new ApplicationEntity();
+ return this.addApplication(null, name);
+ }
+
+ @Override
+ public ApplicationModel addApplication(String id, String name) {
+ MongoApplicationEntity appData = new MongoApplicationEntity();
+ appData.setId(id);
appData.setName(name);
appData.setRealmId(getId());
appData.setEnabled(true);
@@ -671,7 +711,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public boolean removeApplication(String id) {
- return getMongoStore().removeEntity(ApplicationEntity.class, id, invocationContext);
+ return getMongoStore().removeEntity(MongoApplicationEntity.class, id, invocationContext);
}
@Override
@@ -687,16 +727,16 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void grantRole(UserModel user, RoleModel role) {
- UserEntity userEntity = ((UserAdapter) user).getUser();
+ MongoUserEntity userEntity = ((UserAdapter) user).getUser();
getMongoStore().pushItemToList(userEntity, "roleIds", role.getId(), true, invocationContext);
}
@Override
public Set<RoleModel> getRoleMappings(UserModel user) {
Set<RoleModel> result = new HashSet<RoleModel>();
- List<RoleEntity> roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext);
+ List<MongoRoleEntity> roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext);
- for (RoleEntity role : roles) {
+ for (MongoRoleEntity role : roles) {
if (getId().equals(role.getRealmId())) {
result.add(new RoleAdapter(this, role, this, invocationContext));
} else {
@@ -714,7 +754,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
// Filter to retrieve just realm roles TODO: Maybe improve to avoid filter programmatically... Maybe have separate fields for realmRoles and appRoles on user?
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
for (RoleModel role : allRoles) {
- RoleEntity roleEntity = ((RoleAdapter) role).getRole();
+ MongoRoleEntity roleEntity = ((RoleAdapter) role).getRole();
if (getId().equals(roleEntity.getRealmId())) {
realmRoles.add(role);
@@ -727,16 +767,16 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
public void deleteRoleMapping(UserModel user, RoleModel role) {
if (user == null || role == null) return;
- UserEntity userEntity = ((UserAdapter) user).getUser();
+ MongoUserEntity userEntity = ((UserAdapter) user).getUser();
getMongoStore().pullItemFromList(userEntity, "roleIds", role.getId(), invocationContext);
}
@Override
public Set<RoleModel> getScopeMappings(ClientModel client) {
Set<RoleModel> result = new HashSet<RoleModel>();
- List<RoleEntity> roles = MongoModelUtils.getAllScopesOfClient(client, invocationContext);
+ List<MongoRoleEntity> roles = MongoModelUtils.getAllScopesOfClient(client, invocationContext);
- for (RoleEntity role : roles) {
+ for (MongoRoleEntity role : roles) {
if (getId().equals(role.getRealmId())) {
result.add(new RoleAdapter(this, role, this, invocationContext));
} else {
@@ -754,7 +794,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
// Filter to retrieve just realm roles TODO: Maybe improve to avoid filter programmatically... Maybe have separate fields for realmRoles and appRoles on user?
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
for (RoleModel role : allScopes) {
- RoleEntity roleEntity = ((RoleAdapter) role).getRole();
+ MongoRoleEntity roleEntity = ((RoleAdapter) role).getRole();
if (getId().equals(roleEntity.getRealmId())) {
realmRoles.add(role);
@@ -787,7 +827,13 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public OAuthClientModel addOAuthClient(String name) {
- OAuthClientEntity oauthClient = new OAuthClientEntity();
+ return this.addOAuthClient(null, name);
+ }
+
+ @Override
+ public OAuthClientModel addOAuthClient(String id, String name) {
+ MongoOAuthClientEntity oauthClient = new MongoOAuthClientEntity();
+ oauthClient.setId(id);
oauthClient.setRealmId(getId());
oauthClient.setName(name);
getMongoStore().insertEntity(oauthClient, invocationContext);
@@ -797,7 +843,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public boolean removeOAuthClient(String id) {
- return getMongoStore().removeEntity(OAuthClientEntity.class, id, invocationContext);
+ return getMongoStore().removeEntity(MongoOAuthClientEntity.class, id, invocationContext);
}
@Override
@@ -806,13 +852,13 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
.and("realmId").is(getId())
.and("name").is(name)
.get();
- OAuthClientEntity oauthClient = getMongoStore().loadSingleEntity(OAuthClientEntity.class, query, invocationContext);
+ MongoOAuthClientEntity oauthClient = getMongoStore().loadSingleEntity(MongoOAuthClientEntity.class, query, invocationContext);
return oauthClient == null ? null : new OAuthClientAdapter(this, oauthClient, invocationContext);
}
@Override
public OAuthClientModel getOAuthClientById(String id) {
- OAuthClientEntity clientEntity = getMongoStore().loadEntity(OAuthClientEntity.class, id, invocationContext);
+ MongoOAuthClientEntity clientEntity = getMongoStore().loadEntity(MongoOAuthClientEntity.class, id, invocationContext);
// Check if client belongs to this realm
if (clientEntity == null || !getId().equals(clientEntity.getRealmId())) return null;
@@ -825,9 +871,9 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
- List<OAuthClientEntity> results = getMongoStore().loadEntities(OAuthClientEntity.class, query, invocationContext);
+ List<MongoOAuthClientEntity> results = getMongoStore().loadEntities(MongoOAuthClientEntity.class, query, invocationContext);
List<OAuthClientModel> list = new ArrayList<OAuthClientModel>();
- for (OAuthClientEntity data : results) {
+ for (MongoOAuthClientEntity data : results) {
list.add(new OAuthClientAdapter(this, data, invocationContext));
}
return list;
@@ -923,13 +969,8 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void updateCredential(UserModel user, UserCredentialModel cred) {
- CredentialEntity credentialEntity = null;
- UserEntity userEntity = ((UserAdapter) user).getUser();
- for (CredentialEntity entity : userEntity.getCredentials()) {
- if (entity.getType().equals(cred.getType())) {
- credentialEntity = entity;
- }
- }
+ MongoUserEntity userEntity = ((UserAdapter) user).getUser();
+ CredentialEntity credentialEntity = getCredentialEntity(userEntity, cred.getType());
if (credentialEntity == null) {
credentialEntity = new CredentialEntity();
@@ -949,6 +990,52 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
getMongoStore().updateEntity(userEntity, invocationContext);
}
+ private CredentialEntity getCredentialEntity(MongoUserEntity userEntity, String credType) {
+ for (CredentialEntity entity : userEntity.getCredentials()) {
+ if (entity.getType().equals(credType)) {
+ return entity;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public List<UserCredentialValueModel> getCredentialsDirectly(UserModel user) {
+ MongoUserEntity userEntity = ((UserAdapter) user).getUser();
+ List<CredentialEntity> credentials = userEntity.getCredentials();
+ List<UserCredentialValueModel> result = new ArrayList<UserCredentialValueModel>();
+ for (CredentialEntity credEntity : credentials) {
+ UserCredentialValueModel credModel = new UserCredentialValueModel();
+ credModel.setType(credEntity.getType());
+ credModel.setDevice(credEntity.getDevice());
+ credModel.setValue(credEntity.getValue());
+ credModel.setSalt(credEntity.getSalt());
+
+ result.add(credModel);
+ }
+
+ return result;
+ }
+
+ @Override
+ public void updateCredentialDirectly(UserModel user, UserCredentialValueModel credModel) {
+ MongoUserEntity userEntity = ((UserAdapter) user).getUser();
+ CredentialEntity credentialEntity = getCredentialEntity(userEntity, credModel.getType());
+
+ if (credentialEntity == null) {
+ credentialEntity = new CredentialEntity();
+ credentialEntity.setType(credModel.getType());
+ userEntity.getCredentials().add(credentialEntity);
+ }
+
+ credentialEntity.setValue(credModel.getValue());
+ credentialEntity.setSalt(credModel.getSalt());
+ credentialEntity.setDevice(credModel.getDevice());
+
+ getMongoStore().updateEntity(userEntity, invocationContext);
+ }
+
@Override
public UserModel getUserBySocialLink(SocialLinkModel socialLink) {
DBObject query = new QueryBuilder()
@@ -956,13 +1043,13 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
.and("socialLinks.socialUserId").is(socialLink.getSocialUserId())
.and("realmId").is(getId())
.get();
- UserEntity userEntity = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext);
+ MongoUserEntity userEntity = getMongoStore().loadSingleEntity(MongoUserEntity.class, query, invocationContext);
return userEntity == null ? null : new UserAdapter(userEntity, invocationContext);
}
@Override
public Set<SocialLinkModel> getSocialLinks(UserModel user) {
- UserEntity userEntity = ((UserAdapter) user).getUser();
+ MongoUserEntity userEntity = ((UserAdapter) user).getUser();
List<SocialLinkEntity> linkEntities = userEntity.getSocialLinks();
if (linkEntities == null) {
@@ -985,7 +1072,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void addSocialLink(UserModel user, SocialLinkModel socialLink) {
- UserEntity userEntity = ((UserAdapter) user).getUser();
+ MongoUserEntity userEntity = ((UserAdapter) user).getUser();
SocialLinkEntity socialLinkEntity = new SocialLinkEntity();
socialLinkEntity.setSocialProvider(socialLink.getSocialProvider());
socialLinkEntity.setSocialUserId(socialLink.getSocialUserId());
@@ -1000,13 +1087,13 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
if (socialLinkEntity == null) {
return false;
}
- UserEntity userEntity = ((UserAdapter) user).getUser();
+ MongoUserEntity userEntity = ((UserAdapter) user).getUser();
return getMongoStore().pullItemFromList(userEntity, "socialLinks", socialLinkEntity, invocationContext);
}
private SocialLinkEntity findSocialLink(UserModel user, String socialProvider) {
- UserEntity userEntity = ((UserAdapter) user).getUser();
+ MongoUserEntity userEntity = ((UserAdapter) user).getUser();
List<SocialLinkEntity> linkEntities = userEntity.getSocialLinks();
if (linkEntities == null) {
return null;
@@ -1022,7 +1109,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public AuthenticationLinkModel getAuthenticationLink(UserModel user) {
- UserEntity userEntity = ((UserAdapter) user).getUser();
+ MongoUserEntity userEntity = ((UserAdapter) user).getUser();
AuthenticationLinkEntity authLinkEntity = userEntity.getAuthenticationLink();
if (authLinkEntity == null) {
@@ -1034,7 +1121,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
- UserEntity userEntity = ((UserAdapter) user).getUser();
+ MongoUserEntity userEntity = ((UserAdapter) user).getUser();
AuthenticationLinkEntity authLinkEntity = new AuthenticationLinkEntity();
authLinkEntity.setAuthProvider(authenticationLink.getAuthProvider());
authLinkEntity.setAuthUserId(authenticationLink.getAuthUserId());
@@ -1060,7 +1147,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
- List<UserEntity> users = getMongoStore().loadEntities(UserEntity.class, query, invocationContext);
+ List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, query, invocationContext);
return convertUserEntities(users);
}
@@ -1100,7 +1187,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
).get()
);
- List<UserEntity> users = getMongoStore().loadEntities(UserEntity.class, builder.get(), invocationContext);
+ List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, builder.get(), invocationContext);
return convertUserEntities(users);
}
@@ -1122,13 +1209,13 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
queryBuilder.and(UserModel.EMAIL).regex(Pattern.compile("(?i:" + entry.getValue() + "$)"));
}
}
- List<UserEntity> users = getMongoStore().loadEntities(UserEntity.class, queryBuilder.get(), invocationContext);
+ List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, queryBuilder.get(), invocationContext);
return convertUserEntities(users);
}
- protected List<UserModel> convertUserEntities(List<UserEntity> userEntities) {
+ protected List<UserModel> convertUserEntities(List<MongoUserEntity> userEntities) {
List<UserModel> userModels = new ArrayList<UserModel>();
- for (UserEntity user : userEntities) {
+ for (MongoUserEntity user : userEntities) {
userModels.add(new UserAdapter(user, invocationContext));
}
return userModels;
@@ -1232,17 +1319,18 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public ApplicationModel getAdminApp() {
- ApplicationEntity appData = getMongoStore().loadEntity(ApplicationEntity.class, realm.getAdminAppId(), invocationContext);
- return new ApplicationAdapter(this, appData, invocationContext);
+ MongoApplicationEntity appData = getMongoStore().loadEntity(MongoApplicationEntity.class, realm.getAdminAppId(), invocationContext);
+ return appData != null ? new ApplicationAdapter(this, appData, invocationContext) : null;
}
@Override
public void setAdminApp(ApplicationModel app) {
realm.setAdminAppId(app.getId());
+ updateRealm();
}
@Override
- public RealmEntity getMongoEntity() {
+ public MongoRealmEntity getMongoEntity() {
return realm;
}
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java
index 14cd478..3d8ef1a 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java
@@ -10,11 +10,10 @@ import com.mongodb.QueryBuilder;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
-import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity;
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
-import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
-import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
-import org.keycloak.models.mongo.keycloak.entities.RoleEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
@@ -22,17 +21,17 @@ import org.keycloak.models.utils.KeycloakModelUtils;
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class RoleAdapter extends AbstractMongoAdapter<RoleEntity> implements RoleModel {
+public class RoleAdapter extends AbstractMongoAdapter<MongoRoleEntity> implements RoleModel {
- private final RoleEntity role;
+ private final MongoRoleEntity role;
private RoleContainerModel roleContainer;
private RealmModel realm;
- public RoleAdapter(RealmModel realm, RoleEntity roleEntity, MongoStoreInvocationContext invContext) {
+ public RoleAdapter(RealmModel realm, MongoRoleEntity roleEntity, MongoStoreInvocationContext invContext) {
this(realm, roleEntity, null, invContext);
}
- public RoleAdapter(RealmModel realm, RoleEntity roleEntity, RoleContainerModel roleContainer, MongoStoreInvocationContext invContext) {
+ public RoleAdapter(RealmModel realm, MongoRoleEntity roleEntity, RoleContainerModel roleContainer, MongoStoreInvocationContext invContext) {
super(invContext);
this.role = roleEntity;
this.roleContainer = roleContainer;
@@ -94,10 +93,10 @@ public class RoleAdapter extends AbstractMongoAdapter<RoleEntity> implements Rol
DBObject query = new QueryBuilder()
.and("_id").in(role.getCompositeRoleIds())
.get();
- List<RoleEntity> childRoles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext);
+ List<MongoRoleEntity> childRoles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext);
Set<RoleModel> set = new HashSet<RoleModel>();
- for (RoleEntity childRole : childRoles) {
+ for (MongoRoleEntity childRole : childRoles) {
set.add(new RoleAdapter(realm, childRole, invocationContext));
}
return set;
@@ -108,13 +107,13 @@ public class RoleAdapter extends AbstractMongoAdapter<RoleEntity> implements Rol
if (roleContainer == null) {
// Compute it
if (role.getRealmId() != null) {
- RealmEntity realm = getMongoStore().loadEntity(RealmEntity.class, role.getRealmId(), invocationContext);
+ MongoRealmEntity realm = getMongoStore().loadEntity(MongoRealmEntity.class, role.getRealmId(), invocationContext);
if (realm == null) {
throw new IllegalStateException("Realm with id: " + role.getRealmId() + " doesn't exists");
}
roleContainer = new RealmAdapter(realm, invocationContext);
} else if (role.getApplicationId() != null) {
- ApplicationEntity appEntity = getMongoStore().loadEntity(ApplicationEntity.class, role.getApplicationId(), invocationContext);
+ MongoApplicationEntity appEntity = getMongoStore().loadEntity(MongoApplicationEntity.class, role.getApplicationId(), invocationContext);
if (appEntity == null) {
throw new IllegalStateException("Application with id: " + role.getApplicationId() + " doesn't exists");
}
@@ -135,12 +134,12 @@ public class RoleAdapter extends AbstractMongoAdapter<RoleEntity> implements Rol
return KeycloakModelUtils.searchFor(role, this, visited);
}
- public RoleEntity getRole() {
+ public MongoRoleEntity getRole() {
return role;
}
@Override
- public RoleEntity getMongoEntity() {
+ public MongoRoleEntity getMongoEntity() {
return role;
}
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 608082e..c713b9f 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -2,7 +2,7 @@ package org.keycloak.models.mongo.keycloak.adapters;
import org.keycloak.models.UserModel;
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
-import org.keycloak.models.mongo.keycloak.entities.UserEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
import java.util.Collections;
import java.util.HashMap;
@@ -15,11 +15,11 @@ import java.util.Set;
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class UserAdapter extends AbstractMongoAdapter<UserEntity> implements UserModel {
+public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implements UserModel {
- private final UserEntity user;
+ private final MongoUserEntity user;
- public UserAdapter(UserEntity userEntity, MongoStoreInvocationContext invContext) {
+ public UserAdapter(MongoUserEntity userEntity, MongoStoreInvocationContext invContext) {
super(invContext);
this.user = userEntity;
}
@@ -133,7 +133,7 @@ public class UserAdapter extends AbstractMongoAdapter<UserEntity> implements Use
return user.getAttributes()==null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(user.getAttributes());
}
- public UserEntity getUser() {
+ public MongoUserEntity getUser() {
return user;
}
@@ -173,7 +173,7 @@ public class UserAdapter extends AbstractMongoAdapter<UserEntity> implements Use
}
@Override
- public UserEntity getMongoEntity() {
+ public MongoUserEntity getMongoEntity() {
return user;
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UsernameLoginFailureAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UsernameLoginFailureAdapter.java
index 0722945..131a08b 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UsernameLoginFailureAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UsernameLoginFailureAdapter.java
@@ -2,23 +2,22 @@ package org.keycloak.models.mongo.keycloak.adapters;
import org.keycloak.models.UsernameLoginFailureModel;
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
-import org.keycloak.models.mongo.keycloak.entities.UserEntity;
-import org.keycloak.models.mongo.keycloak.entities.UsernameLoginFailureEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoUsernameLoginFailureEntity;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<UsernameLoginFailureEntity> implements UsernameLoginFailureModel {
- protected UsernameLoginFailureEntity user;
+public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<MongoUsernameLoginFailureEntity> implements UsernameLoginFailureModel {
+ protected MongoUsernameLoginFailureEntity user;
- public UsernameLoginFailureAdapter(MongoStoreInvocationContext invocationContext, UsernameLoginFailureEntity user) {
+ public UsernameLoginFailureAdapter(MongoStoreInvocationContext invocationContext, MongoUsernameLoginFailureEntity user) {
super(invocationContext);
this.user = user;
}
@Override
- protected UsernameLoginFailureEntity getMongoEntity() {
+ protected MongoUsernameLoginFailureEntity getMongoEntity() {
return user;
}
@@ -35,6 +34,7 @@ public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<UsernameL
@Override
public void setFailedLoginNotBefore(int notBefore) {
user.setFailedLoginNotBefore(notBefore);
+ updateMongoEntity();
}
@Override
@@ -45,11 +45,13 @@ public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<UsernameL
@Override
public void incrementFailures() {
user.setNumFailures(getNumFailures() + 1);
+ updateMongoEntity();
}
@Override
public void clearFailures() {
user.setNumFailures(0);
+ updateMongoEntity();
}
@Override
@@ -60,6 +62,7 @@ public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<UsernameL
@Override
public void setLastFailure(long lastFailure) {
user.setLastFailure(lastFailure);
+ updateMongoEntity();
}
@Override
@@ -70,4 +73,5 @@ public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<UsernameL
@Override
public void setLastIPFailure(String ip) {
user.setLastIPFailure(ip);
+ updateMongoEntity();
}}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/config/SystemPropertiesMongoClientProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/config/SystemPropertiesMongoClientProvider.java
index 1095195..4635fd4 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/config/SystemPropertiesMongoClientProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/config/SystemPropertiesMongoClientProvider.java
@@ -15,12 +15,12 @@ public class SystemPropertiesMongoClientProvider implements MongoClientProvider
protected static final Logger logger = Logger.getLogger(SystemPropertiesMongoClientProvider.class);
- private static final String MONGO_HOST = "keycloak.mongo.host";
- private static final String MONGO_PORT = "keycloak.mongo.port";
- private static final String MONGO_DB_NAME = "keycloak.mongo.db";
- private static final String MONGO_CLEAR_ON_STARTUP = "keycloak.mongo.clearOnStartup";
+ public static final String MONGO_HOST = "keycloak.mongo.host";
+ public static final String MONGO_PORT = "keycloak.mongo.port";
+ public static final String MONGO_DB_NAME = "keycloak.mongo.db";
+ public static final String MONGO_CLEAR_ON_STARTUP = "keycloak.mongo.clearOnStartup";
- // Property names from Liveoak . Those are used as fallback in case that original value is not available
+ // Property names from Liveoak . Those are used as fallback
private static final String MONGO_HOST_2 = "mongo.host";
private static final String MONGO_PORT_2 = "mongo.port";
private static final String MONGO_DB_NAME_2 = "mongo.db";
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java
new file mode 100755
index 0000000..c4a8d1a
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java
@@ -0,0 +1,26 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.keycloak.models.entities.ApplicationEntity;
+import org.keycloak.models.mongo.api.MongoCollection;
+import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.models.mongo.api.MongoIndex;
+import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@MongoCollection(collectionName = "applications")
+@MongoIndex(fields = { "realmId", "name" }, unique = true)
+public class MongoApplicationEntity extends ApplicationEntity implements MongoIdentifiableEntity {
+
+ @Override
+ public void afterRemove(MongoStoreInvocationContext context) {
+ // Remove all roles, which belongs to this application
+ DBObject query = new QueryBuilder()
+ .and("applicationId").is(getId())
+ .get();
+ context.getMongoStore().removeEntities(MongoRoleEntity.class, query, context);
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
new file mode 100755
index 0000000..1bd2f90
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
@@ -0,0 +1,36 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.keycloak.models.entities.RealmEntity;
+import org.keycloak.models.mongo.api.MongoCollection;
+import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.models.mongo.api.MongoIndex;
+import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@MongoCollection(collectionName = "realms")
+@MongoIndex(fields = { "name" }, unique = true)
+public class MongoRealmEntity extends RealmEntity implements MongoIdentifiableEntity {
+
+ @Override
+ public void afterRemove(MongoStoreInvocationContext context) {
+ DBObject query = new QueryBuilder()
+ .and("realmId").is(getId())
+ .get();
+
+ // Remove all users of this realm
+ context.getMongoStore().removeEntities(MongoUserEntity.class, query, context);
+
+ // Remove all roles of this realm
+ context.getMongoStore().removeEntities(MongoRoleEntity.class, query, context);
+
+ // Remove all applications of this realm
+ context.getMongoStore().removeEntities(MongoApplicationEntity.class, query, context);
+
+ // Remove all clients of this realm
+ context.getMongoStore().removeEntities(MongoOAuthClientEntity.class, query, context);
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java
new file mode 100755
index 0000000..e9dd851
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java
@@ -0,0 +1,32 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import org.keycloak.models.entities.UserEntity;
+import org.keycloak.models.mongo.api.MongoCollection;
+import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.models.mongo.api.MongoIndex;
+import org.keycloak.models.mongo.api.MongoIndexes;
+import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@MongoCollection(collectionName = "users")
+@MongoIndexes({
+ @MongoIndex(fields = { "realmId", "loginName" }, unique = true),
+ @MongoIndex(fields = { "emailIndex" }, unique = true, sparse = true),
+})
+public class MongoUserEntity extends UserEntity implements MongoIdentifiableEntity {
+
+
+ public String getEmailIndex() {
+ return getEmail() != null ? getRealmId() + "//" + getEmail() : null;
+ }
+
+ public void setEmailIndex(String ignored) {
+ }
+
+ @Override
+ public void afterRemove(MongoStoreInvocationContext invocationContext) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUsernameLoginFailureEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUsernameLoginFailureEntity.java
new file mode 100755
index 0000000..62533f5
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUsernameLoginFailureEntity.java
@@ -0,0 +1,18 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import org.keycloak.models.entities.UsernameLoginFailureEntity;
+import org.keycloak.models.mongo.api.MongoCollection;
+import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@MongoCollection(collectionName = "userFailures")
+public class MongoUsernameLoginFailureEntity extends UsernameLoginFailureEntity implements MongoIdentifiableEntity {
+
+ @Override
+ public void afterRemove(MongoStoreInvocationContext invocationContext) {
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java
index d485615..a6d40b9 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java
@@ -7,12 +7,12 @@ import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.entities.ClientEntity;
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.mongo.keycloak.adapters.ClientAdapter;
import org.keycloak.models.mongo.keycloak.adapters.UserAdapter;
-import org.keycloak.models.mongo.keycloak.entities.ClientEntity;
-import org.keycloak.models.mongo.keycloak.entities.RoleEntity;
-import org.keycloak.models.mongo.keycloak.entities.UserEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -20,8 +20,8 @@ import org.keycloak.models.mongo.keycloak.entities.UserEntity;
public class MongoModelUtils {
// Get everything including both application and realm roles
- public static List<RoleEntity> getAllRolesOfUser(UserModel user, MongoStoreInvocationContext invContext) {
- UserEntity userEntity = ((UserAdapter)user).getUser();
+ public static List<MongoRoleEntity> getAllRolesOfUser(UserModel user, MongoStoreInvocationContext invContext) {
+ MongoUserEntity userEntity = ((UserAdapter)user).getUser();
List<String> roleIds = userEntity.getRoleIds();
if (roleIds == null || roleIds.isEmpty()) {
@@ -31,12 +31,12 @@ public class MongoModelUtils {
DBObject query = new QueryBuilder()
.and("_id").in(roleIds)
.get();
- return invContext.getMongoStore().loadEntities(RoleEntity.class, query, invContext);
+ return invContext.getMongoStore().loadEntities(MongoRoleEntity.class, query, invContext);
}
// Get everything including both application and realm scopes
- public static List<RoleEntity> getAllScopesOfClient(ClientModel client, MongoStoreInvocationContext invContext) {
- ClientEntity scopedEntity = ((ClientAdapter)client).getMongoEntity();
+ public static List<MongoRoleEntity> getAllScopesOfClient(ClientModel client, MongoStoreInvocationContext invContext) {
+ ClientEntity scopedEntity = ((ClientAdapter)client).getMongoEntityAsClient();
List<String> scopeIds = scopedEntity.getScopeIds();
if (scopeIds == null || scopeIds.isEmpty()) {
@@ -46,6 +46,6 @@ public class MongoModelUtils {
DBObject query = new QueryBuilder()
.and("_id").in(scopeIds)
.get();
- return invContext.getMongoStore().loadEntities(RoleEntity.class, query, invContext);
+ return invContext.getMongoStore().loadEntities(MongoRoleEntity.class, query, invContext);
}
}
diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java
index 215a9fc..d549fa7 100755
--- a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java
+++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java
@@ -8,12 +8,11 @@ import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class Address implements MongoEntity {
+public class Address {
private String street;
private int number;
- @MongoField
public String getStreet() {
return street;
}
@@ -22,7 +21,6 @@ public class Address implements MongoEntity {
this.street = street;
}
- @MongoField
public int getNumber() {
return number;
}
diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/AddressWithFlats.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/AddressWithFlats.java
index 0495f43..7e13e94 100644
--- a/model/mongo/src/test/java/org/keycloak/models/mongo/test/AddressWithFlats.java
+++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/AddressWithFlats.java
@@ -13,7 +13,6 @@ public class AddressWithFlats extends Address {
private List<String> flatNumbers;
- @MongoField
public List<String> getFlatNumbers() {
return flatNumbers;
}
diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoStoreTest.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoStoreTest.java
index 7200806..f48b372 100755
--- a/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoStoreTest.java
+++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoStoreTest.java
@@ -6,7 +6,6 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
-import org.keycloak.models.mongo.api.MongoEntity;
import org.keycloak.models.mongo.api.MongoStore;
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.mongo.impl.MongoStoreImpl;
@@ -22,7 +21,7 @@ import java.util.List;
*/
public class MongoStoreTest {
- private static final Class<? extends MongoEntity>[] MANAGED_DATA_TYPES = (Class<? extends MongoEntity>[])new Class<?>[] {
+ private static final Class<?>[] MANAGED_DATA_TYPES = (Class<?>[])new Class<?>[] {
Person.class,
Address.class,
AddressWithFlats.class
diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java
index e3f8042..3db6616 100755
--- a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java
+++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java
@@ -1,8 +1,8 @@
package org.keycloak.models.mongo.test;
-import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity;
import org.keycloak.models.mongo.api.MongoCollection;
-import org.keycloak.models.mongo.api.MongoField;
+import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
import java.util.HashMap;
import java.util.List;
@@ -12,8 +12,9 @@ import java.util.Map;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@MongoCollection(collectionName = "persons")
-public class Person extends AbstractMongoIdentifiableEntity {
+public class Person implements MongoIdentifiableEntity {
+ private String id;
private String firstName;
private int age;
private List<String> kids;
@@ -23,7 +24,14 @@ public class Person extends AbstractMongoIdentifiableEntity {
private List<Gender> genders;
private Map<String, String> attributes = new HashMap<String, String>();
- @MongoField
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
public String getFirstName() {
return firstName;
}
@@ -32,7 +40,6 @@ public class Person extends AbstractMongoIdentifiableEntity {
this.firstName = firstName;
}
- @MongoField
public int getAge() {
return age;
}
@@ -41,7 +48,6 @@ public class Person extends AbstractMongoIdentifiableEntity {
this.age = age;
}
- @MongoField
public Gender getGender() {
return gender;
}
@@ -50,7 +56,6 @@ public class Person extends AbstractMongoIdentifiableEntity {
this.gender = gender;
}
- @MongoField
public List<Gender> getGenders() {
return genders;
}
@@ -59,7 +64,6 @@ public class Person extends AbstractMongoIdentifiableEntity {
this.genders = genders;
}
- @MongoField
public List<String> getKids() {
return kids;
}
@@ -68,7 +72,6 @@ public class Person extends AbstractMongoIdentifiableEntity {
this.kids = kids;
}
- @MongoField
public List<AddressWithFlats> getAddresses() {
return addresses;
}
@@ -77,7 +80,6 @@ public class Person extends AbstractMongoIdentifiableEntity {
this.addresses = addresses;
}
- @MongoField
public Address getMainAddress() {
return mainAddress;
}
@@ -86,7 +88,6 @@ public class Person extends AbstractMongoIdentifiableEntity {
this.mainAddress = mainAddress;
}
- @MongoField
public Map<String, String> getAttributes() {
return attributes;
}
@@ -103,6 +104,10 @@ public class Person extends AbstractMongoIdentifiableEntity {
attributes.remove(key);
}
+ @Override
+ public void afterRemove(MongoStoreInvocationContext invocationContext) {
+ }
+
public static enum Gender {
MALE, FEMALE
}
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java b/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java
index 7fcbadb..4270d29 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java
@@ -164,6 +164,9 @@ public class AdapterTest extends AbstractModelTest {
cred.setValue("password");
realmModel.updateCredential(user, cred);
+ commit();
+
+ realmModel = identitySession.getRealm("JUGGLER");
Assert.assertTrue(realmModel.removeUser("bburke"));
Assert.assertFalse(realmModel.removeUser("bburke"));
Assert.assertNull(realmModel.getUser("bburke"));
@@ -218,6 +221,9 @@ public class AdapterTest extends AbstractModelTest {
realmModel.addScopeMapping(app, realmRole);
+ commit();
+ realmModel = identitySession.getRealm("JUGGLER");
+
Assert.assertTrue(realmManager.removeRealm(realmModel));
Assert.assertFalse(realmManager.removeRealm(realmModel));
Assert.assertNull(realmManager.getRealm(realmModel.getId()));
@@ -241,6 +247,10 @@ public class AdapterTest extends AbstractModelTest {
RoleModel realmRole = realmModel.addRole("test");
realmModel.addScopeMapping(app, realmRole);
+ commit();
+ realmModel = identitySession.getRealm("JUGGLER");
+ app = realmModel.getApplicationByName("test-app");
+
Assert.assertTrue(realmModel.removeRoleById(realmRole.getId()));
Assert.assertFalse(realmModel.removeRoleById(realmRole.getId()));
Assert.assertNull(realmModel.getRole(realmRole.getName()));
@@ -521,7 +531,7 @@ public class AdapterTest extends AbstractModelTest {
}
commit(true);
- // Ty to rename realm to duplicate name
+ // Try to rename realm to duplicate name
realmManager.createRealm("JUGGLER2");
commit();
try {
diff --git a/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java
index 4d94084..05fbe78 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java
@@ -64,6 +64,15 @@ public class ApplicationModelTest extends AbstractModelTest {
assertEquals(application, copy);
}
+ @Test
+ public void testAddApplicationWithId() {
+ application = realm.addApplication("app-123", "application2");
+ commit();
+ application = realmManager.getRealm(realm.getId()).getApplicationById("app-123");
+ Assert.assertNotNull(application);
+ }
+
+
public static void assertEquals(ApplicationModel expected, ApplicationModel actual) {
Assert.assertEquals(expected.getName(), actual.getName());
Assert.assertEquals(expected.getBaseUrl(), actual.getBaseUrl());
diff --git a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
index db004b5..0552a97 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
@@ -50,6 +50,16 @@ public class ImportTest extends AbstractModelTest {
commit();
realm = realmManager.getRealm("demo");
+ assertDataImportedInRealm(realm);
+
+ commit();
+
+ realm = realmManager.getRealm("demo");
+ realmManager.removeRealm(realm);
+ }
+
+ // Moved to static method, so it's possible to test this from other places too (for example export-import tests)
+ public static void assertDataImportedInRealm(RealmModel realm) {
Assert.assertTrue(realm.isVerifyEmail());
Assert.assertFalse(realm.isUpdateProfileOnInitialSocialLogin());
@@ -211,13 +221,6 @@ public class ImportTest extends AbstractModelTest {
AuthenticationLinkModel authLink = realm.getAuthenticationLink(socialUser);
Assert.assertEquals(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, authLink.getAuthProvider());
Assert.assertEquals("myUser1", authLink.getAuthUserId());
-
- commit();
-
- realm = realmManager.getRealm("demo");
- realmManager.removeRealm(realm);
-
-
}
@Test
diff --git a/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java b/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java
index c783eaa..35a0981 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java
@@ -48,6 +48,10 @@ public class MultipleRealmsTest extends AbstractModelTest {
// Test searching
Assert.assertEquals(2, realm1.searchForUser("user").size());
+ commit();
+ realm1 = identitySession.getRealm("id1");
+ realm2 = identitySession.getRealm("id2");
+
realm1.removeUser("user1");
realm1.removeUser("user2");
Assert.assertEquals(0, realm1.searchForUser("user").size());
pom.xml 8(+8 -0)
diff --git a/pom.xml b/pom.xml
index 640300d..bc1e568 100755
--- a/pom.xml
+++ b/pom.xml
@@ -102,6 +102,7 @@
<module>timer</module>
<module>bundled-war-example</module>
<module>project-integrations</module>
+ <module>export-import</module>
</modules>
<dependencyManagement>
@@ -323,6 +324,13 @@
<version>1.3.1b</version>
</dependency>
+ <!-- Encrypted ZIP -->
+ <dependency>
+ <groupId>de.idyl</groupId>
+ <artifactId>winzipaes</artifactId>
+ <version>1.0.1</version>
+ </dependency>
+
<!-- Selenium -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
server/pom.xml 15(+15 -0)
diff --git a/server/pom.xml b/server/pom.xml
index 7fb498b..3e29a02 100755
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -225,6 +225,21 @@
<scope>provided</scope>
</dependency>
+ <!-- export/import -->
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-export-import-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-export-import-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>de.idyl</groupId>
+ <artifactId>winzipaes</artifactId>
+ </dependency>
</dependencies>
services/pom.xml 6(+6 -0)
diff --git a/services/pom.xml b/services/pom.xml
index bec5d06..af526e7 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -87,6 +87,12 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
+ <artifactId>keycloak-export-import-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
<artifactId>keycloak-picketlink-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 3012507..158ad59 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -10,6 +10,7 @@ import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.AuditProviderFactory;
import org.keycloak.authentication.AuthenticationProvider;
import org.keycloak.authentication.AuthenticationProviderFactory;
+import org.keycloak.exportimport.ExportImportProvider;
import org.keycloak.models.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -31,6 +32,7 @@ import org.keycloak.models.utils.ModelProviderUtils;
import org.keycloak.timer.TimerProvider;
import org.keycloak.timer.TimerProviderFactory;
import org.keycloak.util.JsonSerialization;
+import org.keycloak.util.ProviderLoader;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Application;
@@ -43,6 +45,7 @@ import java.io.InputStream;
import java.net.URI;
import java.util.Date;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
@@ -92,6 +95,8 @@ public class KeycloakApplication extends Application {
setupScheduledTasks(providerSessionFactory, factory);
importRealms(context);
+
+ checkExportImportProvider();
}
public String getContextPath() {
@@ -267,5 +272,16 @@ public class KeycloakApplication extends Application {
}
}
+ protected void checkExportImportProvider() {
+ Iterator<ExportImportProvider> providers = ProviderLoader.load(ExportImportProvider.class).iterator();
+
+ if (providers.hasNext()) {
+ ExportImportProvider exportImport = providers.next();
+ exportImport.checkExportImport(factory);
+ } else {
+ log.warn("No ExportImportProvider found!");
+ }
+ }
+
}
testsuite/integration/pom.xml 17(+17 -0)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 5566e60..a83b4a7 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -234,6 +234,17 @@
</dependency>
<dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-export-import-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-export-import-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
@@ -346,6 +357,12 @@
<artifactId>picketlink-common</artifactId>
</dependency>
+ <!-- Encrypted ZIP -->
+ <dependency>
+ <groupId>de.idyl</groupId>
+ <artifactId>winzipaes</artifactId>
+ </dependency>
+
<!-- This adds couple of other dependencies (like picketlink) -->
<dependency>
<groupId>org.keycloak</groupId>