keycloak-memoizeit

Details

diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProvider.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProvider.java
index 56bc812..1aa0b5e 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProvider.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProvider.java
@@ -13,4 +13,10 @@ public interface ImportProvider extends Provider {
     void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException;
 
     void importRealm(KeycloakSessionFactory factory, String realmName, Strategy strategy) throws IOException;
+
+    /**
+     * @return true if master realm was previously exported and is available in the data to be imported
+     * @throws IOException
+     */
+    boolean isMasterRealmExported() throws IOException;
 }
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
index fbd909d..4940820 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
@@ -21,6 +21,7 @@ import org.keycloak.representations.idm.UserRepresentation;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.keycloak.exportimport.ExportImportConfig;
@@ -143,6 +144,15 @@ public class ImportUtils {
      * @throws IOException
      */
     public static void importFromStream(KeycloakSession session, ObjectMapper mapper, InputStream is, Strategy strategy) throws IOException {
+        Map<String, RealmRepresentation> realmReps = getRealmsFromStream(mapper, is);
+        for (RealmRepresentation realmRep : realmReps.values()) {
+            importRealm(session, realmRep, strategy);
+        }
+    }
+
+    public static Map<String, RealmRepresentation> getRealmsFromStream(ObjectMapper mapper, InputStream is) throws IOException {
+        Map<String, RealmRepresentation> result = new HashMap<String, RealmRepresentation>();
+
         JsonFactory factory = mapper.getJsonFactory();
         JsonParser parser = factory.createJsonParser(is);
         try {
@@ -166,18 +176,21 @@ public class ImportUtils {
                 }
 
                 for (RealmRepresentation realmRep : realmReps) {
-                    importRealm(session, realmRep, strategy);
+                    result.put(realmRep.getId(), realmRep);
                 }
             } else if (parser.getCurrentToken() == JsonToken.START_OBJECT) {
                 // Case with single realm in stream
                 RealmRepresentation realmRep = parser.readValueAs(RealmRepresentation.class);
-                importRealm(session, realmRep, strategy);
+                result.put(realmRep.getId(), realmRep);
             }
         } finally {
             parser.close();
         }
+
+        return result;
     }
 
+
     // Assuming that it's invoked inside transaction
     public static void importUsersFromStream(KeycloakSession session, String realmName, ObjectMapper mapper, InputStream is) throws IOException {
         RealmProvider model = session.realms();
diff --git a/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
index 15d6dc8..9a77264 100755
--- a/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
+++ b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
@@ -49,6 +49,20 @@ public class DirImportProvider implements ImportProvider {
 
     @Override
     public void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException {
+        List<String> realmNames = getRealmsToImport();
+
+        for (String realmName : realmNames) {
+            importRealm(factory, realmName, strategy);
+        }
+    }
+
+    @Override
+    public boolean isMasterRealmExported() throws IOException {
+        List<String> realmNames = getRealmsToImport();
+        return realmNames.contains(Config.getAdminRealm());
+    }
+
+    private List<String> getRealmsToImport() throws IOException {
         File[] realmFiles = this.rootDirectory.listFiles(new FilenameFilter() {
 
             @Override
@@ -70,10 +84,7 @@ public class DirImportProvider implements ImportProvider {
                 realmNames.add(realmName);
             }
         }
-
-        for (String realmName : realmNames) {
-            importRealm(factory, realmName, strategy);
-        }
+        return realmNames;
     }
 
     @Override
diff --git a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java
index 8e993d0..62ae93b 100755
--- a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java
+++ b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java
@@ -1,6 +1,7 @@
 package org.keycloak.exportimport.singlefile;
 
 import org.jboss.logging.Logger;
+import org.keycloak.Config;
 import org.keycloak.exportimport.ImportProvider;
 import org.keycloak.exportimport.Strategy;
 import org.keycloak.exportimport.util.ExportImportSessionTask;
@@ -8,11 +9,13 @@ import org.keycloak.exportimport.util.ImportUtils;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.util.JsonSerialization;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -23,6 +26,9 @@ public class SingleFileImportProvider implements ImportProvider {
 
     private File file;
 
+    // Allows to cache representation per provider to avoid parsing them twice
+    protected Map<String, RealmRepresentation> realmReps;
+
     public SingleFileImportProvider(File file) {
         this.file = file;
     }
@@ -30,18 +36,34 @@ public class SingleFileImportProvider implements ImportProvider {
     @Override
     public void importModel(KeycloakSessionFactory factory, final Strategy strategy) throws IOException {
         logger.infof("Full importing from file %s", this.file.getAbsolutePath());
+        checkRealmReps();
+
         KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
 
             @Override
             protected void runExportImportTask(KeycloakSession session) throws IOException {
-                FileInputStream is = new FileInputStream(file);
-                ImportUtils.importFromStream(session, JsonSerialization.mapper, is, strategy);
+                for (RealmRepresentation realmRep : realmReps.values()) {
+                    ImportUtils.importRealm(session, realmRep, strategy);
+                }
             }
 
         });
     }
 
     @Override
+    public boolean isMasterRealmExported() throws IOException {
+        checkRealmReps();
+        return (realmReps.containsKey(Config.getAdminRealm()));
+    }
+
+    protected void checkRealmReps() throws IOException {
+        if (realmReps == null) {
+            FileInputStream is = new FileInputStream(file);
+            realmReps = ImportUtils.getRealmsFromStream(JsonSerialization.mapper, is);
+        }
+    }
+
+    @Override
     public void importRealm(KeycloakSessionFactory factory, String realmName, Strategy strategy) throws IOException {
         // TODO: import just that single realm in case that file contains many realms?
         importModel(factory, strategy);
diff --git a/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java
index fb40139..c9e6178 100755
--- a/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java
+++ b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java
@@ -51,6 +51,20 @@ public class ZipImportProvider implements ImportProvider {
 
     @Override
     public void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException {
+        List<String> realmNames = getRealmsToImport();
+
+        for (String realmName : realmNames) {
+            importRealm(factory, realmName, strategy);
+        }
+    }
+
+    @Override
+    public boolean isMasterRealmExported() throws IOException {
+        List<String> realmNames = getRealmsToImport();
+        return realmNames.contains(Config.getAdminRealm());
+    }
+
+    private List<String> getRealmsToImport() throws IOException {
         List<String> realmNames = new ArrayList<String>();
         for (ExtZipEntry entry : this.decrypter.getEntryList()) {
             String entryName = entry.getName();
@@ -66,10 +80,7 @@ public class ZipImportProvider implements ImportProvider {
                 }
             }
         }
-
-        for (String realmName : realmNames) {
-            importRealm(factory, realmName, strategy);
-        }
+        return realmNames;
     }
 
     @Override
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index aafb174..b20e794 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -1649,7 +1649,8 @@ module.directive('kcProviderConfig', function ($modal) {
         scope: {
             config: '=',
             properties: '=',
-            realm: '='
+            realm: '=',
+            clients: '='
         },
         restrict: 'E',
         replace: true,
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html
index c837139..aa17065 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html
@@ -45,28 +45,8 @@
                 </div>
                 <kc-tooltip>{{mapperType.helpText}}</kc-tooltip>
             </div>
-            <div data-ng-repeat="option in mapperType.properties" class="form-group">
-                <label class="col-md-2 control-label">{{option.label}}</label>
-
-                <div class="col-sm-4" data-ng-show="option.type == 'String'">
-                    <input class="form-control" type="text" data-ng-model="mapper.config[ option.name ]">
-                </div>
-                <div class="col-sm-4" data-ng-show="option.type == 'boolean'">
-                    <input ng-model="mapper.config[ option.name ]" value="'true'" name="option.name" id="option.name" onoffswitchmodel />
-                </div>
-                <div class="col-sm-4" data-ng-show="option.type == 'List'">
-                    <select ng-model="mapper.config[ option.name ]" ng-options="data for data in option.defaultValue">
-                        <option value="" selected> Select one... </option>
-                    </select>
-                </div>
-                <div class="col-sm-4" data-ng-show="option.type == 'ClientList'">
-                    <select ng-model="mapper.config[ option.name ]" ng-options="client.clientId as client.clientId for client in clients">
-                        <option value="" selected> Select one... </option>
-                    </select>
-                </div>
-                <kc-tooltip>{{option.helpText}}</kc-tooltip>
-            </div>
 
+            <kc-provider-config config="mapper.config" properties="mapperType.properties" realm="realm" clients="clients"></kc-provider-config>
         </fieldset>
         <div class="pull-right form-actions" data-ng-show="create && access.manageRealm">
             <button kc-cancel data-ng-click="cancel()">Cancel</button>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-provider-config.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-provider-config.html
index 0dd4f95..9268939 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-provider-config.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-provider-config.html
@@ -1,7 +1,7 @@
 <div data-ng-repeat="option in properties" class="form-group">
     <label class="col-md-2 control-label">{{option.label}}</label>
 
-    <div class="col-sm-6" data-ng-hide="option.type == 'boolean' || option.type == 'List' || option.type == 'Role'">
+    <div class="col-sm-6" data-ng-hide="option.type == 'boolean' || option.type == 'List' || option.type == 'Role' || option.type == 'ClientList'">
         <input class="form-control" type="text" data-ng-model="config[ option.name ]" >
     </div>
     <div class="col-sm-6" data-ng-show="option.type == 'boolean'">
@@ -18,6 +18,11 @@
     <div class="col-sm-4" data-ng-show="option.type == 'Role'">
         <button type="submit" data-ng-click="openRoleSelector(option.name)" class="btn btn-default" tooltip="Enter role in the textbox to the left, or click this button to browse and select the role you want">Select Role</button>
     </div>
+    <div class="col-sm-4" data-ng-show="option.type == 'ClientList'">
+        <select ng-model="config[ option.name ]" ng-options="client.clientId as client.clientId for client in clients">
+            <option value="" selected> Select one... </option>
+        </select>
+    </div>
 
     <kc-tooltip>{{option.helpText}}</kc-tooltip>
 </div>
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 1aff2ed..a0e1235 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -21,7 +21,9 @@ import org.keycloak.models.utils.DefaultAuthenticationFlows;
 import org.keycloak.models.utils.DefaultRequiredActions;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.representations.idm.ApplicationRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.OAuthClientRepresentation;
 import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.timer.TimerProvider;
@@ -267,13 +269,27 @@ public class RealmManager {
         setupMasterAdminManagement(realm);
         if (!hasRealmAdminManagementClient(rep)) setupRealmAdminManagement(realm);
         if (!hasAccountManagementClient(rep)) setupAccountManagement(realm);
-        if (!hasImpersonationServiceClient(rep)) setupImpersonationService(realm);
+
+        boolean postponeImpersonationSetup = false;
+        if (!hasImpersonationServiceClient(rep)) {
+            if (hasRealmAdminManagementClient(rep)) {
+                postponeImpersonationSetup = true;
+            } else {
+                setupImpersonationService(realm);
+            }
+        }
 
         if (!hasBrokerClient(rep)) setupBrokerService(realm);
         if (!hasAdminConsoleClient(rep)) setupAdminConsole(realm);
 
         RepresentationToModel.importRealm(session, rep, realm);
 
+        // Could happen when migrating from older version and I have exported JSON file, which contains "realm-management" client but not "impersonation" client
+        // I need to postpone impersonation because it needs "realm-management" client and it's roles set
+        if (postponeImpersonationSetup) {
+            setupImpersonationService(realm);
+        }
+
         setupAuthenticationFlows(realm);
         setupRequiredActions(realm);
 
@@ -287,50 +303,49 @@ public class RealmManager {
     }
 
     private boolean hasRealmAdminManagementClient(RealmRepresentation rep) {
-        if (rep.getClients() == null) return false;
-        for (ClientRepresentation clientRep : rep.getClients()) {
-            if (clientRep.getClientId().equals(getRealmAdminClientId(rep))) {
-                return true;
-            }
-        }
-        return false;
+        String realmAdminClientId = getRealmAdminClientId(rep);
+        return hasClient(rep, realmAdminClientId);
     }
 
     private boolean hasAccountManagementClient(RealmRepresentation rep) {
-        if (rep.getClients() == null) return false;
-        for (ClientRepresentation clientRep : rep.getClients()) {
-            if (clientRep.getClientId().equals(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)) {
-                return true;
-            }
-        }
-        return false;
+        return hasClient(rep, Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
     }
     private boolean hasImpersonationServiceClient(RealmRepresentation rep) {
-        if (rep.getClients() == null) return false;
-        for (ClientRepresentation clientRep : rep.getClients()) {
-            if (clientRep.getClientId().equals(Constants.IMPERSONATION_SERVICE_CLIENT_ID)) {
-                return true;
-            }
-        }
-        return false;
+        return hasClient(rep, Constants.IMPERSONATION_SERVICE_CLIENT_ID);
     }
     private boolean hasBrokerClient(RealmRepresentation rep) {
-        if (rep.getClients() == null) return false;
-        for (ClientRepresentation clientRep : rep.getClients()) {
-            if (clientRep.getClientId().equals(Constants.BROKER_SERVICE_CLIENT_ID)) {
-                return true;
-            }
-        }
-        return false;
+        return hasClient(rep, Constants.BROKER_SERVICE_CLIENT_ID);
     }
 
     private boolean hasAdminConsoleClient(RealmRepresentation rep) {
-        if (rep.getClients() == null) return false;
-        for (ClientRepresentation clientRep : rep.getClients()) {
-            if (clientRep.getClientId().equals(Constants.ADMIN_CONSOLE_CLIENT_ID)) {
-                return true;
+        return hasClient(rep, Constants.ADMIN_CONSOLE_CLIENT_ID);
+    }
+
+    private boolean hasClient(RealmRepresentation rep, String clientId) {
+        if (rep.getClients() != null) {
+            for (ClientRepresentation clientRep : rep.getClients()) {
+                if (clientRep.getClientId().equals(clientId)) {
+                    return true;
+                }
             }
         }
+
+        // TODO: Just for compatibility with old versions. Should be removed later...
+        if (rep.getApplications() != null) {
+            for (ApplicationRepresentation clientRep : rep.getApplications()) {
+                if (clientRep.getName().equals(clientId)) {
+                    return true;
+                }
+            }
+        }
+        if (rep.getOauthClients() != null) {
+            for (OAuthClientRepresentation clientRep : rep.getOauthClients()) {
+                if (clientRep.getName().equals(clientId)) {
+                    return true;
+                }
+            }
+        }
+
         return false;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
index 2d14b4b..5b5a611 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
@@ -168,12 +168,8 @@ public class RealmsAdminResource {
         for (InputPart inputPart : inputParts) {
             // inputPart.getBody doesn't work as content-type is wrong, and inputPart.setMediaType is not supported on AS7 (RestEasy 2.3.2.Final)
             rep = JsonSerialization.readValue(inputPart.getBodyAsString(), RealmRepresentation.class);
-            RealmModel realm;
-            try {
-                realm = realmManager.importRealm(rep);
-            } catch (ModelDuplicateException e) {
-                return ErrorResponse.exists("Realm " + rep.getRealm() + " already exists");
-            }
+
+            RealmModel realm = realmManager.importRealm(rep);
 
             grantPermissionsToRealmCreator(realm);
             
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 0601882..875511e 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -83,7 +83,7 @@ public class KeycloakApplication extends Application {
         classes.add(JsResource.class);
         classes.add(WelcomeResource.class);
 
-        new ExportImportManager().checkExportImport(this.sessionFactory);
+        new ExportImportManager().checkExportImport(this.sessionFactory, context.getContextPath());
 
         setupDefaultRealm(context.getContextPath());