keycloak-aplcache

Changes

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/PartialImportRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/PartialImportRepresentation.java
new file mode 100644
index 0000000..9b8c2ae
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/PartialImportRepresentation.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.representations.idm;
+
+import java.util.List;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+
+/**
+ * Used for partial import of users, clients, roles, and identity providers.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+@JsonIgnoreProperties(ignoreUnknown=true)
+public class PartialImportRepresentation {
+    public enum Policy { SKIP, OVERWRITE, FAIL };
+
+    protected Policy policy = Policy.FAIL;
+    protected String ifResourceExists = "";
+    protected List<UserRepresentation> users;
+    protected List<ClientRepresentation> clients;
+    protected List<IdentityProviderRepresentation> identityProviders;
+    protected RolesRepresentation roles;
+
+    public boolean hasUsers() {
+        return (users != null) && !users.isEmpty();
+    }
+
+    public boolean hasClients() {
+        return (clients != null) && !clients.isEmpty();
+    }
+
+    public boolean hasIdps() {
+        return (identityProviders != null) && !identityProviders.isEmpty();
+    }
+
+    public boolean hasRealmRoles() {
+        return (roles != null) && (roles.getRealm() != null) && (!roles.getRealm().isEmpty());
+    }
+
+    public boolean hasClientRoles() {
+        return (roles != null) && (roles.getClient() != null) && (!roles.getClient().isEmpty());
+    }
+
+    public String getIfResourceExists() {
+        return ifResourceExists;
+    }
+
+    public void setIfResourceExists(String ifResourceExists) {
+        this.ifResourceExists = ifResourceExists;
+        this.policy = Policy.valueOf(ifResourceExists);
+    }
+
+    public Policy getPolicy() {
+        return this.policy;
+    }
+
+    public List<UserRepresentation> getUsers() {
+        return users;
+    }
+
+    public void setUsers(List<UserRepresentation> users) {
+        this.users = users;
+    }
+
+    public List<ClientRepresentation> getClients() {
+        return clients;
+    }
+
+    public void setClients(List<ClientRepresentation> clients) {
+        this.clients = clients;
+    }
+
+    public List<IdentityProviderRepresentation> getIdentityProviders() {
+        return identityProviders;
+    }
+
+    public void setIdentityProviders(List<IdentityProviderRepresentation> identityProviders) {
+        this.identityProviders = identityProviders;
+    }
+
+    public RolesRepresentation getRoles() {
+        return roles;
+    }
+
+    public void setRoles(RolesRepresentation roles) {
+        this.roles = roles;
+    }
+}
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/export-import.xml b/docbook/auth-server-docs/reference/en/en-US/modules/export-import.xml
index fbd6016..6ec8660 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/export-import.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/export-import.xml
@@ -1,114 +1,134 @@
 <chapter id="export-import">
     <title>Export and Import</title>
-    <para>
-        Export/import is useful especially if you want to migrate your whole Keycloak database from one environment to another or migrate to different database (For example from MySQL to Oracle).
-        You can trigger export/import at startup of Keycloak server and it's configurable with System properties right now. The fact it's done at server startup means that no-one can access Keycloak UI or REST endpoints
-        and edit Keycloak database on the fly when export or import is in progress. Otherwise it could lead to inconsistent results.
-    </para>
-    <para>
-        You can export/import your database either to:
-        <itemizedlist>
-            <listitem>Directory on local filesystem</listitem>
-            <listitem>Single JSON file on your filesystem</listitem>
-        </itemizedlist>
+    <section>
+        <title>Startup export/import</title>
+        <para>
+            Export/import is useful especially if you want to migrate your whole Keycloak database from one environment to another or migrate to different database (For example from MySQL to Oracle).
+            You can trigger export/import at startup of Keycloak server and it's configurable with System properties right now. The fact it's done at server startup means that no-one can access Keycloak UI or REST endpoints
+            and edit Keycloak database on the fly when export or import is in progress. Otherwise it could lead to inconsistent results.
+        </para>
+        <para>
+            You can export/import your database either to:
+            <itemizedlist>
+                <listitem>Directory on local filesystem</listitem>
+                <listitem>Single JSON file on your filesystem</listitem>
+            </itemizedlist>
 
-        When importing using the "dir" strategy, note that the files need to follow the naming convention specified below.
-        If you are importing files which were previously exported, the files already follow this convention.
-        <itemizedlist>
-            <listitem>{REALM_NAME}-realm.json, such as "acme-roadrunner-affairs-realm.json" for the realm named "acme-roadrunner-affairs"</listitem>
-            <listitem>{REALM_NAME}-users-{INDEX}.json, such as "acme-roadrunner-affairs-users-0.json" for the first users file of the realm named "acme-roadrunner-affairs"</listitem>
-        </itemizedlist>
-    </para>
-    <para>
-        If you import to Directory, you can specify also the number of users to be stored in each JSON file. So if you have
-        very large amount of users in your database, you likely don't want to import them into single file as the file might be very big.
-        Processing of each file is done in separate transaction as exporting/importing all users at once could also lead to memory issues.
-    </para>
-    <para>
-        To export into unencrypted directory you can use:
-        <programlisting><![CDATA[
+            When importing using the "dir" strategy, note that the files need to follow the naming convention specified below.
+            If you are importing files which were previously exported, the files already follow this convention.
+            <itemizedlist>
+                <listitem>{REALM_NAME}-realm.json, such as "acme-roadrunner-affairs-realm.json" for the realm named "acme-roadrunner-affairs"</listitem>
+                <listitem>{REALM_NAME}-users-{INDEX}.json, such as "acme-roadrunner-affairs-users-0.json" for the first users file of the realm named "acme-roadrunner-affairs"</listitem>
+            </itemizedlist>
+        </para>
+        <para>
+            If you import to Directory, you can specify also the number of users to be stored in each JSON file. So if you have
+            very large amount of users in your database, you likely don't want to import them into single file as the file might be very big.
+            Processing of each file is done in separate transaction as exporting/importing all users at once could also lead to memory issues.
+        </para>
+        <para>
+            To export into unencrypted directory you can use:
+            <programlisting><![CDATA[
 bin/standalone.sh -Dkeycloak.migration.action=export
 -Dkeycloak.migration.provider=dir -Dkeycloak.migration.dir=<DIR TO EXPORT TO>
 ]]></programlisting>
-        And similarly for import just use <literal>-Dkeycloak.migration.action=import</literal> instead of <literal>export</literal> .
-    </para>
-    <para>
-        To export into single JSON file you can use:
-        <programlisting><![CDATA[
+            And similarly for import just use <literal>-Dkeycloak.migration.action=import</literal> instead of <literal>export</literal> .
+        </para>
+        <para>
+            To export into single JSON file you can use:
+            <programlisting><![CDATA[
 bin/standalone.sh -Dkeycloak.migration.action=export
 -Dkeycloak.migration.provider=singleFile -Dkeycloak.migration.file=<FILE TO EXPORT TO>
 ]]></programlisting>
-    </para>
-    <para>
-        Here's an example of importing:
-        <programlisting><![CDATA[
+        </para>
+        <para>
+            Here's an example of importing:
+            <programlisting><![CDATA[
 bin/standalone.sh -Dkeycloak.migration.action=import
 -Dkeycloak.migration.provider=singleFile -Dkeycloak.migration.file=<FILE TO IMPORT>
 -Dkeycloak.migration.strategy=OVERWRITE_EXISTING
 ]]></programlisting>
-    </para>
-    <para>
-        Other available options are:
-        <variablelist>
-            <varlistentry>
-                <term>-Dkeycloak.migration.realmName</term>
-                <listitem>
-                    <para>
-                        can be used if you want to export just one specified realm instead of all.
-                        If not specified, then all realms will be exported.
-                    </para>
-                </listitem>
-            </varlistentry>
-            <varlistentry>
-                <term>-Dkeycloak.migration.usersExportStrategy</term>
-                <listitem>
-                    <para>
-                        can be used to specify for Directory providers to specify where to import users.
-                        Possible values are:
-                        <itemizedlist>
-                            <listitem>DIFFERENT_FILES - Users will be exported into more different files according to maximum number of users per file. This is default value</listitem>
-                            <listitem>SKIP - exporting of users will be skipped completely</listitem>
-                            <listitem>REALM_FILE - All users will be exported to same file with realm (So file like "foo-realm.json" with both realm data and users)</listitem>
-                            <listitem>SAME_FILE - All users will be exported to same file but different than realm (So file like "foo-realm.json" with realm data and "foo-users.json" with users)</listitem>
-                        </itemizedlist>
-                    </para>
-                </listitem>
-            </varlistentry>
-            <varlistentry>
-                <term>-Dkeycloak.migration.usersPerFile</term>
-                <listitem>
-                    <para>
-                        can be used to specify number of users per file (and also per DB transaction).
-                        It's 5000 by default. It's used only if usersExportStrategy is DIFFERENT_FILES
-                    </para>
-                </listitem>
-            </varlistentry>
-            <varlistentry>
-                <term>-Dkeycloak.migration.strategy</term>
-                <listitem>
-                    <para>
-                        is used during import. It can be used to specify how to proceed if realm with same name
-                        already exists in the database where you are going to import data. Possible values are:
-                        <itemizedlist>
-                            <listitem>IGNORE_EXISTING - Ignore importing if realm of this name already exists</listitem>
-                            <listitem>OVERWRITE_EXISTING - Remove existing realm and import it again with new data from JSON file.
-                                If you want to fully migrate one environment to another and ensure that the new environment will contain same data
-                                like the old one, you can specify this.
-                            </listitem>
-                        </itemizedlist>
-                    </para>
-                </listitem>
-            </varlistentry>
-        </variablelist>
-    </para>
-    <para>
-        When importing realm files that weren't exported before, the option <literal>keycloak.import</literal> can be used. If more than one realm
-        file needs to be imported, a comma separated list of file names can be specified. This is more appropriate than the cases before, as this
-        will happen only after the master realm has been initialized. Examples:
-        <itemizedlist>
-            <listitem>-Dkeycloak.import=/tmp/realm1.json</listitem>
-            <listitem>-Dkeycloak.import=/tmp/realm1.json,/tmp/realm2.json</listitem>
-        </itemizedlist>
-    </para>
-
+        </para>
+        <para>
+            Other available options are:
+            <variablelist>
+                <varlistentry>
+                    <term>-Dkeycloak.migration.realmName</term>
+                    <listitem>
+                        <para>
+                            can be used if you want to export just one specified realm instead of all.
+                            If not specified, then all realms will be exported.
+                        </para>
+                    </listitem>
+                </varlistentry>
+                <varlistentry>
+                    <term>-Dkeycloak.migration.usersExportStrategy</term>
+                    <listitem>
+                        <para>
+                            can be used to specify for Directory providers to specify where to import users.
+                            Possible values are:
+                            <itemizedlist>
+                                <listitem>DIFFERENT_FILES - Users will be exported into more different files according to maximum number of users per file. This is default value</listitem>
+                                <listitem>SKIP - exporting of users will be skipped completely</listitem>
+                                <listitem>REALM_FILE - All users will be exported to same file with realm (So file like "foo-realm.json" with both realm data and users)</listitem>
+                                <listitem>SAME_FILE - All users will be exported to same file but different than realm (So file like "foo-realm.json" with realm data and "foo-users.json" with users)</listitem>
+                            </itemizedlist>
+                        </para>
+                    </listitem>
+                </varlistentry>
+                <varlistentry>
+                    <term>-Dkeycloak.migration.usersPerFile</term>
+                    <listitem>
+                        <para>
+                            can be used to specify number of users per file (and also per DB transaction).
+                            It's 5000 by default. It's used only if usersExportStrategy is DIFFERENT_FILES
+                        </para>
+                    </listitem>
+                </varlistentry>
+                <varlistentry>
+                    <term>-Dkeycloak.migration.strategy</term>
+                    <listitem>
+                        <para>
+                            is used during import. It can be used to specify how to proceed if realm with same name
+                            already exists in the database where you are going to import data. Possible values are:
+                            <itemizedlist>
+                                <listitem>IGNORE_EXISTING - Ignore importing if realm of this name already exists</listitem>
+                                <listitem>OVERWRITE_EXISTING - Remove existing realm and import it again with new data from JSON file.
+                                    If you want to fully migrate one environment to another and ensure that the new environment will contain same data
+                                    like the old one, you can specify this.
+                                </listitem>
+                            </itemizedlist>
+                        </para>
+                    </listitem>
+                </varlistentry>
+            </variablelist>
+        </para>
+        <para>
+            When importing realm files that weren't exported before, the option <literal>keycloak.import</literal> can be used. If more than one realm
+            file needs to be imported, a comma separated list of file names can be specified. This is more appropriate than the cases before, as this
+            will happen only after the master realm has been initialized. Examples:
+            <itemizedlist>
+                <listitem>-Dkeycloak.import=/tmp/realm1.json</listitem>
+                <listitem>-Dkeycloak.import=/tmp/realm1.json,/tmp/realm2.json</listitem>
+            </itemizedlist>
+        </para>
+    </section>
+    <section>
+        <title>Admin console export/import</title>
+        <para>
+            Import of most resources can be performed from the admin console.
+            Exporting resources will be supported in future versions.
+        </para>
+        <para>
+            The files created during a "startup" export can be used to import from
+            the admin UI.  This way, you can export from one realm and import to
+            another realm.  Or, you can export from one server and import to another.
+        </para>
+        <warning>
+            <para>
+                The admin console import allows you to "overwrite" resources if you choose.
+                Use this feature with caution, especially on a production system.
+            </para>
+        </warning>
+    </section>
 </chapter>
\ No newline at end of file
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 be30adf..69df83e 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
@@ -406,6 +406,16 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'RealmEventsConfigCtrl'
         })
+        .when('/realms/:realm/partial-import', {
+            templateUrl : resourceUrl + '/partials/partial-import.html',
+            resolve : {
+                resourceName : function() { return 'users'},
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                }
+            },
+            controller : 'RealmImportCtrl'
+        })
         .when('/create/user/:realm', {
             templateUrl : resourceUrl + '/partials/user-detail.html',
             resolve : {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 445b8d0..a3c99e8 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -2062,14 +2062,210 @@ module.controller('ClientInitialAccessCreateCtrl', function($scope, realm, Clien
     };
 });
 
+module.controller('RealmImportCtrl', function($scope, realm, $route, 
+                                              Notifications, $modal, $resource) {
+    $scope.rawContent = {};
+    $scope.fileContent = {
+        enabled: true
+    };
+    $scope.changed = false;
+    $scope.files = [];
+    $scope.realm = realm;
+    $scope.overwrite = false;
+    $scope.skip = false;
+    $scope.importUsers = false;
+    $scope.importClients = false;
+    $scope.importIdentityProviders = false;
+    $scope.importRealmRoles = false;
+    $scope.importClientRoles = false;
+    $scope.ifResourceExists='FAIL';
+    $scope.isMultiRealm = false;
+    $scope.results = {};
+    $scope.currentPage = 0;
+    var pageSize = 15;
+    
+    var oldCopy = angular.copy($scope.fileContent);
 
+    $scope.importFile = function($fileContent){
+        var parsed;
+        try {
+            parsed = JSON.parse($fileContent);
+        } catch (e) {
+            Notifications.error('Unable to parse JSON file.');
+            return;
+        }
+        
+        $scope.rawContent = angular.copy(parsed);
+        if (($scope.rawContent instanceof Array) && ($scope.rawContent.length > 0)) {
+            if ($scope.rawContent.length > 1) $scope.isMultiRealm = true;
+            $scope.fileContent = $scope.rawContent[0];
+        } else {
+            $scope.fileContent = $scope.rawContent;
+        }
+        
+        $scope.importing = true;
+        $scope.importUsers = $scope.hasArray('users');
+        $scope.importClients = $scope.hasArray('clients');
+        $scope.importIdentityProviders = $scope.hasArray('identityProviders');
+        $scope.importRealmRoles = $scope.hasRealmRoles();
+        $scope.importClientRoles = $scope.hasClientRoles();
+        $scope.results = {};
+        if (!$scope.hasResources()) {
+            $scope.nothingToImport();
+        }
+    };
 
+    $scope.hasResults = function() {
+        return (Object.keys($scope.results).length > 0) &&
+                ($scope.results.results !== undefined) &&
+                ($scope.results.results.length > 0);
+    }
+    
+    $scope.resultsPage = function() {
+        if (!$scope.hasResults()) return {};
+        return $scope.results.results.slice(startIndex(), endIndex());
+    }
+    
+    function startIndex() {
+        return pageSize * $scope.currentPage;
+    }
+    
+    function endIndex() {
+        var length = $scope.results.results.length;
+        var endIndex = startIndex() + pageSize;
+        if (endIndex > length) endIndex = length;
+        return endIndex;
+    }
+    
+    $scope.setFirstPage = function() {
+        $scope.currentPage = 0;
+    }
+    
+    $scope.setNextPage = function() {
+        $scope.currentPage++;
+    }
+    
+    $scope.setPreviousPage = function() {
+        $scope.currentPage--;
+    }
+    
+    $scope.hasNext = function() {
+        if (!$scope.hasResults()) return false;
+        var length = $scope.results.results.length;
+        //console.log('length=' + length);
+        var endIndex = startIndex() + pageSize;
+        //console.log('endIndex=' + endIndex);
+        return length > endIndex;
+    }
+    
+    $scope.hasPrevious = function() {
+        if (!$scope.hasResults()) return false;
+        return $scope.currentPage > 0;
+    }
+    
+    $scope.viewImportDetails = function() {
+        $modal.open({
+            templateUrl: resourceUrl + '/partials/modal/view-object.html',
+            controller: 'ObjectModalCtrl',
+            resolve: {
+                object: function () {
+                    return $scope.fileContent;
+                }
+            }
+        })
+    };
+    
+    $scope.hasArray = function(section) {
+        return ($scope.fileContent !== 'undefined') &&
+               ($scope.fileContent.hasOwnProperty(section)) &&
+               ($scope.fileContent[section] instanceof Array) &&
+               ($scope.fileContent[section].length > 0);
+    }
+    
+    $scope.hasRealmRoles = function() {
+        return $scope.hasRoles() &&
+               ($scope.fileContent.roles.hasOwnProperty('realm')) &&
+               ($scope.fileContent.roles.realm instanceof Array) &&
+               ($scope.fileContent.roles.realm.length > 0);
+    }
+    
+    $scope.hasRoles = function() {
+        return ($scope.fileContent !== 'undefined') &&
+               ($scope.fileContent.hasOwnProperty('roles')) &&
+               ($scope.fileContent.roles !== 'undefined');
+    }
+    
+    $scope.hasClientRoles = function() {
+        return $scope.hasRoles() &&
+               ($scope.fileContent.roles.hasOwnProperty('client')) &&
+               (Object.keys($scope.fileContent.roles.client).length > 0);
+    }
+    
+    $scope.itemCount = function(section) {
+        if (!$scope.importing) return 0;
+        if ($scope.hasRealmRoles() && (section === 'roles.realm')) return $scope.fileContent.roles.realm.length;
+        if ($scope.hasClientRoles() && (section === 'roles.client')) return Object.keys($scope.fileContent.roles.client).length;
+        
+        if (!$scope.fileContent.hasOwnProperty(section)) return 0;
+        
+        return $scope.fileContent[section].length;
+    }
+    
+    $scope.hasResources = function() {
+        return ($scope.importUsers && $scope.hasArray('users')) ||
+               ($scope.importClients && $scope.hasArray('clients')) ||
+               ($scope.importIdentityProviders && $scope.hasArray('identityProviders')) ||
+               ($scope.importRealmRoles && $scope.hasRealmRoles()) ||
+               ($scope.importClientRoles && $scope.hasClientRoles());
+    }
+    
+    $scope.nothingToImport = function() {
+        Notifications.error('No resouces specified to import.');
+    }
+    
+    $scope.$watch('fileContent', function() {
+        if (!angular.equals($scope.fileContent, oldCopy)) {
+            $scope.changed = true;
+        }
+    }, true);
+    
+    $scope.successMessage = function() {
+        var message = $scope.results.added + ' records added. ';
+        if ($scope.ifResourceExists === 'SKIP') {
+            message += $scope.results.skipped + ' records skipped.'
+        }
+        if ($scope.ifResourceExists === 'OVERWRITE') {
+            message += $scope.results.overwritten + ' records overwritten.';
+        }
+        return message;
+    }
+    
+    $scope.save = function() {
+        var json = angular.copy($scope.fileContent);
+        json.ifResourceExists = $scope.ifResourceExists;
+        if (!$scope.importUsers) delete json.users;
+        if (!$scope.importIdentityProviders) delete json.identityProviders;
+        if (!$scope.importClients) delete json.clients;
+        
+        if (json.hasOwnProperty('roles')) {
+            if (!$scope.importRealmRoles) delete json.roles.realm;
+            if (!$scope.importClientRoles) delete json.roles.client;
+        }
+        
+        var importFile = $resource(authUrl + '/admin/realms/' + realm.realm + '/partialImport');
+        $scope.results = importFile.save(json, function() {
+            Notifications.success($scope.successMessage());
+        }, function(error) {
+            if (error.data.errorMessage) {
+                Notifications.error(error.data.errorMessage);
+            } else {
+                Notifications.error('Unexpected error during import');
+            }
+        });
+    };
+    
+    $scope.reset = function() {
+        $route.reload();
+    }
 
-
-
-
-
-
-
-
-
+});
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/partial-import.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/partial-import.html
new file mode 100644
index 0000000..d15cd71
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/partial-import.html
@@ -0,0 +1,120 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+    <h1>Partial Import</h1>
+
+    <form class="form-horizontal" name="partialImportForm" novalidate>
+        <fieldset class="border-top">
+            <div class="form-group">
+                <label for="name" class="col-sm-2 control-label">File</label>
+
+                <div class="col-md-6" data-ng-hide="importing">
+                    <label for="import-file" class="btn btn-default">{{:: 'select-file'| translate}} <i class="pficon pficon-import"></i></label>
+                    <input id="import-file" type="file" class="hidden" kc-on-read-file="importFile($fileContent)"/>
+                </div>
+
+                <div class="col-md-6" data-ng-show="importing">
+                    <button class="btn btn-default" data-ng-click="viewImportDetails()">{{:: 'view-details'| translate}}</button>
+                    <button class="btn btn-default" data-ng-click="reset()">{{:: 'clear-import'| translate}}</button>
+                </div>
+            </div>
+
+            <div class="form-group" data-ng-show="importing && isMultiRealm && !hasResults()">
+                <label for="fromRealm" class="col-md-2 control-label">Import from realm</label>
+                <div class="col-md-2">
+                    <div>
+                        <select id="fromRealm" ng-model="fileContent" class="form-control" 
+                                ng-options="item as item.realm for item in rawContent">
+                        </select>
+                    </div>
+                </div>
+            </div>
+
+            <div class="form-group" data-ng-show="importing && hasArray('users') && !hasResults()">
+                <label class="col-md-2 control-label" for="importUsers">Import Users ({{itemCount('users')}})</label>
+                <div class="col-sm-6">
+                    <input ng-model="importUsers" name="importUsers" id="importUsers" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
+                </div>
+            </div>
+
+            <div class="form-group" data-ng-show="importing && hasArray('clients') && !hasResults()">
+                <label class="col-md-2 control-label" for="importClients">Import Clients ({{itemCount('clients')}})</label>
+                <div class="col-sm-6">
+                    <input ng-model="importClients" name="importClients" id="importClients" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
+                </div>
+            </div>
+
+            <div class="form-group" data-ng-show="importing && hasArray('identityProviders') && !hasResults()">
+                <label class="col-md-2 control-label" for="importIdentityProviders">Import Identity Providers ({{itemCount('identityProviders')}})</label>
+                <div class="col-sm-6">
+                    <input ng-model="importIdentityProviders" name="importIdentityProviders" id="importIdentityProviders" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
+                </div>
+            </div>
+
+            <div class="form-group" data-ng-show="importing && hasRealmRoles() && !hasResults()">
+                <label class="col-md-2 control-label" for="importRealmRoles">Import Realm Roles ({{itemCount('roles.realm')}})</label>
+                <div class="col-sm-6">
+                    <input ng-model="importRealmRoles" name="importRealmRoles" id="importRealmRoles" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
+                </div>
+            </div>
+
+            <div class="form-group" data-ng-show="importing && hasClientRoles() && !hasResults()">
+                <label class="col-md-2 control-label" for="importClientRoles">Import Client Roles ({{itemCount('roles.client')}})</label>
+                <div class="col-sm-6">
+                    <input ng-model="importClientRoles" name="importClientRoles" id="importClientRoles" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
+                </div>
+            </div>
+
+            <div class="form-group" data-ng-show="importing && hasResources() && !hasResults()">
+                <label for="ifResourceExists" class="col-md-2 control-label">If a resource exists</label>
+                <div class="col-md-2">
+                    <div>
+                        <select id="ifResourceExists" ng-model="ifResourceExists" class="form-control">
+                            <option value="FAIL">Fail</option>
+                            <option value="SKIP">Skip</option>
+                            <option value="OVERWRITE">Overwrite</option>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>Specify what should be done if you try to import a resource that already exists.</kc-tooltip>
+            </div>
+        </fieldset>
+
+        <div class="form-group" data-ng-show="importing && hasResources() && !hasResults()">
+            <div class="col-md-10 col-md-offset-2">
+                <button kc-save data-ng-disabled="!changed">{{:: 'import'| translate}}</button>
+            </div>
+        </div>
+
+        <div class="form-group" data-ng-show="hasResults()">
+            {{successMessage()}}
+            <table class="table table-striped table-bordered">
+                <thead>
+                    <tr>
+                        <th>Action</th>
+                        <th>Type</th>
+                        <th>Name</th>
+                        <th>Id</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr ng-repeat="result in resultsPage()" >
+                        <td ng-show="result.action == 'OVERWRITTEN'"><span class="label label-danger">{{result.action}}</span></td>
+                        <td ng-show="result.action == 'SKIPPED'"><span class="label label-warning">{{result.action}}</span></td>
+                        <td ng-show="result.action == 'ADDED'"><span class="label label-success">{{result.action}}</span></td>
+                        <td>{{result.resourceType}}</td>
+                        <td>{{result.resourceName}}</td>
+                        <td>{{result.id}}</td>
+                    </tr>
+                </tbody>
+            </table>
+            
+            <div class="table-nav">
+                <button data-ng-click="setFirstPage()" class="first" ng-disabled="">First page</button>
+                <button data-ng-click="setPreviousPage()" class="prev" ng-disabled="!hasPrevious()">Previous page</button>
+                <button data-ng-click="setNextPage()" class="next" ng-disabled="!hasNext()">Next page</button>
+            </div>
+        </div>
+    </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
index 5904fd7..dabb36c 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
@@ -45,6 +45,7 @@
             <li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users"><span class="pficon pficon-user"></span> Users</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/realm"><i class="fa fa-clock-o"></i> Sessions</a></li>
             <li data-ng-show="access.viewEvents" data-ng-class="(path[2] == 'events' || path[2] == 'events-settings') && 'active'"><a href="#/realms/{{realm.realm}}/events"><i class="fa fa-calendar"></i> Events</a></li>
+            <li data-ng-show="access.manageRealm" ng-class="(path[2] =='partial-import') && 'active'"><a href="#/realms/{{realm.realm}}/partial-import"><span class="pficon pficon-import"></span> Import</a></li>
         </ul>
     </div>
 </div>
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index f40f962..87394a1 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -66,6 +66,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import org.keycloak.representations.idm.RolesRepresentation;
 
 public class RepresentationToModel {
 
@@ -195,47 +196,7 @@ public class RepresentationToModel {
             createClients(session, rep, newRealm);
         }
 
-        if (rep.getRoles() != null) {
-            if (rep.getRoles().getRealm() != null) { // realm roles
-                for (RoleRepresentation roleRep : rep.getRoles().getRealm()) {
-                    createRole(newRealm, roleRep);
-                }
-            }
-            if (rep.getRoles().getClient() != null) {
-                for (Map.Entry<String, List<RoleRepresentation>> entry : rep.getRoles().getClient().entrySet()) {
-                    ClientModel client = newRealm.getClientByClientId(entry.getKey());
-                    if (client == null) {
-                        throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey());
-                    }
-                    for (RoleRepresentation roleRep : entry.getValue()) {
-                        // Application role may already exists (for example if it is defaultRole)
-                        RoleModel role = roleRep.getId()!=null ? client.addRole(roleRep.getId(), roleRep.getName()) : client.addRole(roleRep.getName());
-                        role.setDescription(roleRep.getDescription());
-                        boolean scopeParamRequired = roleRep.isScopeParamRequired()==null ? false : roleRep.isScopeParamRequired();
-                        role.setScopeParamRequired(scopeParamRequired);
-                    }
-                }
-            }
-            // now that all roles are created, re-iterate and set up composites
-            if (rep.getRoles().getRealm() != null) { // realm roles
-                for (RoleRepresentation roleRep : rep.getRoles().getRealm()) {
-                    RoleModel role = newRealm.getRole(roleRep.getName());
-                    addComposites(role, roleRep, newRealm);
-                }
-            }
-            if (rep.getRoles().getClient() != null) {
-                for (Map.Entry<String, List<RoleRepresentation>> entry : rep.getRoles().getClient().entrySet()) {
-                    ClientModel client = newRealm.getClientByClientId(entry.getKey());
-                    if (client == null) {
-                        throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey());
-                    }
-                    for (RoleRepresentation roleRep : entry.getValue()) {
-                        RoleModel role = client.getRole(roleRep.getName());
-                        addComposites(role, roleRep, newRealm);
-                    }
-                }
-            }
-        }
+        importRoles(rep.getRoles(), newRealm);
 
         // Setup realm default roles
         if (rep.getDefaultRoles() != null) {
@@ -356,6 +317,50 @@ public class RepresentationToModel {
         }
     }
 
+    public static void importRoles(RolesRepresentation realmRoles, RealmModel realm) {
+        if (realmRoles == null) return;
+
+        if (realmRoles.getRealm() != null) { // realm roles
+            for (RoleRepresentation roleRep : realmRoles.getRealm()) {
+                createRole(realm, roleRep);
+            }
+        }
+        if (realmRoles.getClient() != null) {
+            for (Map.Entry<String, List<RoleRepresentation>> entry : realmRoles.getClient().entrySet()) {
+                ClientModel client = realm.getClientByClientId(entry.getKey());
+                if (client == null) {
+                    throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey());
+                }
+                for (RoleRepresentation roleRep : entry.getValue()) {
+                    // Application role may already exists (for example if it is defaultRole)
+                    RoleModel role = roleRep.getId()!=null ? client.addRole(roleRep.getId(), roleRep.getName()) : client.addRole(roleRep.getName());
+                    role.setDescription(roleRep.getDescription());
+                    boolean scopeParamRequired = roleRep.isScopeParamRequired()==null ? false : roleRep.isScopeParamRequired();
+                    role.setScopeParamRequired(scopeParamRequired);
+                }
+            }
+        }
+        // now that all roles are created, re-iterate and set up composites
+        if (realmRoles.getRealm() != null) { // realm roles
+            for (RoleRepresentation roleRep : realmRoles.getRealm()) {
+                RoleModel role = realm.getRole(roleRep.getName());
+                addComposites(role, roleRep, realm);
+            }
+        }
+        if (realmRoles.getClient() != null) {
+            for (Map.Entry<String, List<RoleRepresentation>> entry : realmRoles.getClient().entrySet()) {
+                ClientModel client = realm.getClientByClientId(entry.getKey());
+                if (client == null) {
+                    throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey());
+                }
+                for (RoleRepresentation roleRep : entry.getValue()) {
+                    RoleModel role = client.getRole(roleRep.getName());
+                    addComposites(role, roleRep, realm);
+                }
+            }
+        }
+    }
+
     public static void importGroups(RealmModel realm, RealmRepresentation rep) {
         List<GroupRepresentation> groups = rep.getGroups();
         if (groups == null) return;
@@ -639,15 +644,15 @@ public class RepresentationToModel {
         if (rep.getAccountTheme() != null) realm.setAccountTheme(rep.getAccountTheme());
         if (rep.getAdminTheme() != null) realm.setAdminTheme(rep.getAdminTheme());
         if (rep.getEmailTheme() != null) realm.setEmailTheme(rep.getEmailTheme());
-        
+
         if (rep.isEventsEnabled() != null) realm.setEventsEnabled(rep.isEventsEnabled());
         if (rep.getEventsExpiration() != null) realm.setEventsExpiration(rep.getEventsExpiration());
         if (rep.getEventsListeners() != null) realm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
         if (rep.getEnabledEventTypes() != null) realm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes()));
-        
+
         if (rep.isAdminEventsEnabled() != null) realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
         if (rep.isAdminEventsDetailsEnabled() != null) realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
-        
+
 
         if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
         if (rep.getOtpPolicyType() != null) realm.setOTPPolicy(toPolicy(rep));
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 36fd1fd..f44abe8 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -76,9 +76,12 @@ public class JpaUserProvider implements UserProvider {
                 userModel.joinGroup(g);
             }
         }
-        for (RequiredActionProviderModel r : realm.getRequiredActionProviders()) {
-            if (r.isEnabled() && r.isDefaultAction()) {
-                userModel.addRequiredAction(r.getAlias());
+
+        if (addDefaultRequiredActions){
+            for (RequiredActionProviderModel r : realm.getRequiredActionProviders()) {
+                if (r.isEnabled() && r.isDefaultAction()) {
+                    userModel.addRequiredAction(r.getAlias());
+                }
             }
         }
 
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 ce72bba..d5be946 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
@@ -1059,6 +1059,7 @@ public class RealmAdapter implements RealmModel {
         em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", roleEntity.getId()).executeUpdate();
 
         em.remove(roleEntity);
+        em.flush();
 
         return true;
     }
@@ -1217,7 +1218,7 @@ public class RealmAdapter implements RealmModel {
         realm.setEventsListeners(listeners);
         em.flush();
     }
-    
+
     @Override
     public Set<String> getEnabledEventTypes() {
         return realm.getEnabledEventTypes();
@@ -1228,7 +1229,7 @@ public class RealmAdapter implements RealmModel {
         realm.setEnabledEventTypes(enabledEventTypes);
         em.flush();
     }
-    
+
     @Override
     public boolean isAdminEventsEnabled() {
         return realm.isAdminEventsEnabled();
@@ -1250,7 +1251,7 @@ public class RealmAdapter implements RealmModel {
         realm.setAdminEventsDetailsEnabled(enabled);
         em.flush();
     }
-    
+
     @Override
     public ClientModel getMasterAdminClient() {
         ClientEntity masterAdminClient = realm.getMasterAdminClient();
diff --git a/services/src/main/java/org/keycloak/partialimport/AbstractPartialImport.java b/services/src/main/java/org/keycloak/partialimport/AbstractPartialImport.java
new file mode 100644
index 0000000..33f9c12
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/AbstractPartialImport.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.partialimport;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.ws.rs.core.Response;
+import org.jboss.logging.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.PartialImportRepresentation;
+import org.keycloak.services.ErrorResponse;
+
+/**
+ * Base PartialImport for most resource types.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public abstract class AbstractPartialImport<T> implements PartialImport<T> {
+    protected static Logger logger = Logger.getLogger(AbstractPartialImport.class);
+
+    protected final Set<T> toOverwrite = new HashSet<>();
+    protected final Set<T> toSkip = new HashSet<>();
+
+    public abstract List<T> getRepList(PartialImportRepresentation partialImportRep);
+    public abstract String getName(T resourceRep);
+    public abstract String getModelId(RealmModel realm, KeycloakSession session, T resourceRep);
+    public abstract boolean exists(RealmModel realm, KeycloakSession session, T resourceRep);
+    public abstract String existsMessage(T resourceRep);
+    public abstract ResourceType getResourceType();
+    public abstract void remove(RealmModel realm, KeycloakSession session, T resourceRep);
+    public abstract void create(RealmModel realm, KeycloakSession session, T resourceRep);
+
+    @Override
+    public void prepare(PartialImportRepresentation partialImportRep,
+                         RealmModel realm,
+                         KeycloakSession session) throws ErrorResponseException {
+        List<T> repList = getRepList(partialImportRep);
+        if ((repList == null) || repList.isEmpty()) return;
+
+        for (T resourceRep : getRepList(partialImportRep)) {
+            if (exists(realm, session, resourceRep)) {
+                switch (partialImportRep.getPolicy()) {
+                    case SKIP: toSkip.add(resourceRep); break;
+                    case OVERWRITE: toOverwrite.add(resourceRep); break;
+                    default: throw existsError(existsMessage(resourceRep));
+                }
+            }
+        }
+    }
+
+    protected ErrorResponseException existsError(String message) {
+        Response error = ErrorResponse.exists(message);
+        return new ErrorResponseException(error);
+    }
+
+    protected PartialImportResult overwritten(String modelId, T resourceRep){
+        return PartialImportResult.overwritten(getResourceType(), getName(resourceRep), modelId, resourceRep);
+    }
+
+    protected PartialImportResult skipped(String modelId, T resourceRep) {
+        return PartialImportResult.skipped(getResourceType(), getName(resourceRep), modelId, resourceRep);
+    }
+
+    protected PartialImportResult added(String modelId, T resourceRep) {
+        return PartialImportResult.added(getResourceType(), getName(resourceRep), modelId, resourceRep);
+    }
+
+    @Override
+    public void removeOverwrites(RealmModel realm, KeycloakSession session) {
+        for (T resourceRep : toOverwrite) {
+            remove(realm, session, resourceRep);
+        }
+    }
+
+    @Override
+    public PartialImportResults doImport(PartialImportRepresentation partialImportRep, RealmModel realm, KeycloakSession session) throws ErrorResponseException {
+        PartialImportResults results = new PartialImportResults();
+        List<T> repList = getRepList(partialImportRep);
+        if ((repList == null) || repList.isEmpty()) return results;
+
+        for (T resourceRep : toOverwrite) {
+            try {
+                create(realm, session, resourceRep);
+            } catch (Exception e) {
+                logger.error("Error overwriting " + getName(resourceRep), e);
+                throw new ErrorResponseException(ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR));
+            }
+
+            String modelId = getModelId(realm, session, resourceRep);
+            results.addResult(overwritten(modelId, resourceRep));
+        }
+
+        for (T resourceRep : toSkip) {
+            String modelId = getModelId(realm, session, resourceRep);
+            results.addResult(skipped(modelId, resourceRep));
+        }
+
+        for (T resourceRep : repList) {
+            if (toOverwrite.contains(resourceRep)) continue;
+            if (toSkip.contains(resourceRep)) continue;
+
+            try {
+                create(realm, session, resourceRep);
+                String modelId = getModelId(realm, session, resourceRep);
+                results.addResult(added(modelId, resourceRep));
+            } catch (Exception e) {
+                logger.error("Error creating " + getName(resourceRep), e);
+                throw new ErrorResponseException(ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR));
+            }
+        }
+
+        return results;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/Action.java b/services/src/main/java/org/keycloak/partialimport/Action.java
new file mode 100644
index 0000000..86ea54f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/Action.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.partialimport;
+
+/**
+ * Enum for actions taken by PartialImport.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public enum Action {
+    ADDED, SKIPPED, OVERWRITTEN
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/ClientRolesPartialImport.java b/services/src/main/java/org/keycloak/partialimport/ClientRolesPartialImport.java
new file mode 100644
index 0000000..d309b51
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/ClientRolesPartialImport.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.partialimport;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.ws.rs.core.Response;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.PartialImportRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.services.ErrorResponse;
+
+/**
+ * Partial Import handler for Client Roles.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class ClientRolesPartialImport {
+    private final Map<String, Set<RoleRepresentation>> toOverwrite = new HashMap<>();
+    private final Map<String, Set<RoleRepresentation>> toSkip = new HashMap<>();
+
+    public Map<String, Set<RoleRepresentation>> getToOverwrite() {
+        return this.toOverwrite;
+    }
+
+    public Map<String, Set<RoleRepresentation>> getToSkip() {
+        return this.toSkip;
+    }
+
+    public Map<String, List<RoleRepresentation>> getRepList(PartialImportRepresentation partialImportRep) {
+        if (partialImportRep.getRoles() == null) return null;
+        return partialImportRep.getRoles().getClient();
+    }
+
+    public String getName(RoleRepresentation roleRep) {
+        if (roleRep.getName() == null)
+            throw new IllegalStateException("Client role to import does not have a name");
+        return roleRep.getName();
+    }
+
+    public String getCombinedName(String clientId, RoleRepresentation roleRep) {
+        return clientId + "-->" + getName(roleRep);
+    }
+
+    public boolean exists(RealmModel realm, KeycloakSession session, String clientId, RoleRepresentation roleRep) {
+        ClientModel client = realm.getClientByClientId(clientId);
+        if (client == null) return false;
+
+        for (RoleModel role : client.getRoles()) {
+            if (getName(roleRep).equals(role.getName())) return true;
+        }
+
+        return false;
+    }
+
+    // check if client currently exists or will exists as a result of this partial import
+    private boolean clientExists(PartialImportRepresentation partialImportRep, RealmModel realm, String clientId) {
+        if (realm.getClientByClientId(clientId) != null) return true;
+
+        if (partialImportRep.getClients() == null) return false;
+
+        for (ClientRepresentation client : partialImportRep.getClients()) {
+            if (clientId.equals(client.getClientId())) return true;
+        }
+
+        return false;
+    }
+
+    public String existsMessage(String clientId, RoleRepresentation roleRep) {
+        return "Client role '" + getName(roleRep) + "' for client '" + clientId + "' already exists.";
+    }
+
+    public ResourceType getResourceType() {
+        return ResourceType.CLIENT_ROLE;
+    }
+
+    public void deleteRole(RealmModel realm, String clientId, RoleRepresentation roleRep) {
+        ClientModel client = realm.getClientByClientId(clientId);
+        if (client == null) {
+            // client might have been removed as part of this partial import
+            return;
+        }
+        RoleModel role = client.getRole(getName(roleRep));
+        client.removeRole(role);
+    }
+
+    public void prepare(PartialImportRepresentation partialImportRep, RealmModel realm, KeycloakSession session) throws ErrorResponseException {
+        Map<String, List<RoleRepresentation>> repList = getRepList(partialImportRep);
+        if (repList == null || repList.isEmpty()) return;
+
+        for (String clientId : repList.keySet()) {
+            if (!clientExists(partialImportRep, realm, clientId)) {
+                throw noClientFound(clientId);
+            }
+
+            toOverwrite.put(clientId, new HashSet<RoleRepresentation>());
+            toSkip.put(clientId, new HashSet<RoleRepresentation>());
+            for (RoleRepresentation roleRep : repList.get(clientId)) {
+                if (exists(realm, session, clientId, roleRep)) {
+                    switch (partialImportRep.getPolicy()) {
+                        case SKIP:
+                            toSkip.get(clientId).add(roleRep);
+                            break;
+                        case OVERWRITE:
+                            toOverwrite.get(clientId).add(roleRep);
+                            break;
+                        default:
+                            throw exists(existsMessage(clientId, roleRep));
+                    }
+                }
+            }
+        }
+    }
+
+    protected ErrorResponseException exists(String message) {
+        Response error = ErrorResponse.exists(message);
+        return new ErrorResponseException(error);
+    }
+
+    protected ErrorResponseException noClientFound(String clientId) {
+        String message = "Can not import client roles for nonexistent client named " + clientId;
+        Response error = ErrorResponse.error(message, Response.Status.PRECONDITION_FAILED);
+        return new ErrorResponseException(error);
+    }
+
+    public PartialImportResult overwritten(String clientId, String modelId, RoleRepresentation roleRep) {
+        return PartialImportResult.overwritten(getResourceType(), getCombinedName(clientId, roleRep), modelId, roleRep);
+    }
+
+    public PartialImportResult skipped(String clientId, String modelId, RoleRepresentation roleRep) {
+        return PartialImportResult.skipped(getResourceType(), getCombinedName(clientId, roleRep), modelId, roleRep);
+    }
+
+    public PartialImportResult added(String clientId, String modelId, RoleRepresentation roleRep) {
+        return PartialImportResult.added(getResourceType(), getCombinedName(clientId, roleRep), modelId, roleRep);
+    }
+
+    public String getModelId(RealmModel realm, String clientId) {
+        return realm.getClientByClientId(clientId).getId();
+    }
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/ClientsPartialImport.java b/services/src/main/java/org/keycloak/partialimport/ClientsPartialImport.java
new file mode 100644
index 0000000..c04ab46
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/ClientsPartialImport.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.partialimport;
+
+import java.util.List;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.PartialImportRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.services.managers.ClientManager;
+import org.keycloak.services.managers.RealmManager;
+
+/**
+ * PartialImport handler for Clients.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class ClientsPartialImport extends AbstractPartialImport<ClientRepresentation> {
+
+    @Override
+    public List<ClientRepresentation> getRepList(PartialImportRepresentation partialImportRep) {
+        return partialImportRep.getClients();
+    }
+
+    @Override
+    public String getName(ClientRepresentation clientRep) {
+        return clientRep.getClientId();
+    }
+
+    @Override
+    public String getModelId(RealmModel realm, KeycloakSession session, ClientRepresentation clientRep) {
+        return realm.getClientByClientId(getName(clientRep)).getId();
+    }
+
+    @Override
+    public boolean exists(RealmModel realm, KeycloakSession session, ClientRepresentation clientRep) {
+        return realm.getClientByClientId(getName(clientRep)) != null;
+    }
+
+    @Override
+    public String existsMessage(ClientRepresentation clientRep) {
+        return "Client id '" + getName(clientRep) + "' already exists";
+    }
+
+    @Override
+    public ResourceType getResourceType() {
+        return ResourceType.CLIENT;
+    }
+
+    @Override
+    public void remove(RealmModel realm, KeycloakSession session, ClientRepresentation clientRep) {
+        ClientModel clientModel = realm.getClientByClientId(getName(clientRep));
+        new ClientManager(new RealmManager(session)).removeClient(realm, clientModel);
+    }
+
+    @Override
+    public void create(RealmModel realm, KeycloakSession session, ClientRepresentation clientRep) {
+        clientRep.setId(KeycloakModelUtils.generateId());
+
+        List<ProtocolMapperRepresentation> mappers = clientRep.getProtocolMappers();
+        if (mappers != null) {
+            for (ProtocolMapperRepresentation mapper : mappers) {
+                mapper.setId(KeycloakModelUtils.generateId());
+            }
+        }
+
+        RepresentationToModel.createClient(session, realm, clientRep, true);
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/ErrorResponseException.java b/services/src/main/java/org/keycloak/partialimport/ErrorResponseException.java
new file mode 100644
index 0000000..b273f56
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/ErrorResponseException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.partialimport;
+
+import javax.ws.rs.core.Response;
+
+
+/**
+ * An exception that can hold a Response object.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class ErrorResponseException extends Exception {
+    private final Response response;
+
+    public ErrorResponseException(Response response) {
+        this.response = response;
+    }
+
+    public Response getResponse() {
+        return response;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/IdentityProvidersPartialImport.java b/services/src/main/java/org/keycloak/partialimport/IdentityProvidersPartialImport.java
new file mode 100644
index 0000000..59e1153
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/IdentityProvidersPartialImport.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.partialimport;
+
+import java.util.List;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.PartialImportRepresentation;
+
+/**
+ * PartialImport handler for Identitiy Providers.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class IdentityProvidersPartialImport extends AbstractPartialImport<IdentityProviderRepresentation> {
+
+    @Override
+    public List<IdentityProviderRepresentation> getRepList(PartialImportRepresentation partialImportRep) {
+        return partialImportRep.getIdentityProviders();
+    }
+
+    @Override
+    public String getName(IdentityProviderRepresentation idpRep) {
+        return idpRep.getAlias();
+    }
+
+    @Override
+    public String getModelId(RealmModel realm, KeycloakSession session, IdentityProviderRepresentation idpRep) {
+        return realm.getIdentityProviderByAlias(getName(idpRep)).getInternalId();
+    }
+
+    @Override
+    public boolean exists(RealmModel realm, KeycloakSession session, IdentityProviderRepresentation idpRep) {
+        return realm.getIdentityProviderByAlias(getName(idpRep)) != null;
+    }
+
+    @Override
+    public String existsMessage(IdentityProviderRepresentation idpRep) {
+        return "Identity Provider '" + getName(idpRep) + "' already exists.";
+    }
+
+    @Override
+    public ResourceType getResourceType() {
+        return ResourceType.IDP;
+    }
+
+    @Override
+    public void remove(RealmModel realm, KeycloakSession session, IdentityProviderRepresentation idpRep) {
+        realm.removeIdentityProviderByAlias(getName(idpRep));
+    }
+
+    @Override
+    public void create(RealmModel realm, KeycloakSession session, IdentityProviderRepresentation idpRep) {
+        idpRep.setInternalId(KeycloakModelUtils.generateId());
+        IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, idpRep);
+        realm.addIdentityProvider(identityProvider);
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/PartialImport.java b/services/src/main/java/org/keycloak/partialimport/PartialImport.java
new file mode 100644
index 0000000..eaae106
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/PartialImport.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.partialimport;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.PartialImportRepresentation;
+
+/**
+ * Main interface for PartialImport handlers.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public interface PartialImport<T> {
+
+    /**
+     * Find which resources will need to be skipped or overwritten.  Also,
+     * do a preliminary check for errors.
+     *
+     * @param rep Everything in the PartialImport request.
+     * @param realm Realm to be imported into.
+     * @param session The KeycloakSession.
+     * @throws ErrorResponseException If the PartialImport can not be performed,
+     *                                throw this exception.
+     */
+    public void prepare(PartialImportRepresentation rep,
+                         RealmModel realm,
+                         KeycloakSession session) throws ErrorResponseException;
+
+    /**
+     * Delete resources that will be overwritten.  This is done separately so
+     * that it can be called for all resource types before calling all the doImports.
+     *
+     * It was found that doing delete/add per resource causes errors because of
+     * cascading deletes.
+     *
+     * @param realm Realm to be imported into.
+     * @param session The KeycloakSession
+     */
+    public void removeOverwrites(RealmModel realm, KeycloakSession session);
+
+    /**
+     * Create (or re-create) all the imported resources.
+     *
+     * @param rep Everything in the PartialImport request.
+     * @param realm Realm to be imported into.
+     * @param session The KeycloakSession.
+     * @return The final results of the PartialImport request.
+     * @throws ErrorResponseException if an error was detected trying to doImport a resource.
+     */
+    public PartialImportResults doImport(PartialImportRepresentation rep,
+                                         RealmModel realm,
+                                         KeycloakSession session) throws ErrorResponseException;
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java b/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java
new file mode 100644
index 0000000..1bc391f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.partialimport;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.core.Response;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.PartialImportRepresentation;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
+
+/**
+ * This class manages the PartialImport handlers.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class PartialImportManager {
+    private final List<PartialImport> partialImports = new ArrayList<>();
+
+    private final PartialImportRepresentation rep;
+    private final KeycloakSession session;
+    private final RealmModel realm;
+    private final AdminEventBuilder adminEvent;
+
+    public PartialImportManager(PartialImportRepresentation rep, KeycloakSession session,
+                                RealmModel realm, AdminEventBuilder adminEvent) {
+        this.rep = rep;
+        this.session = session;
+        this.realm = realm;
+        this.adminEvent = adminEvent;
+
+        // Do not change the order of these!!!
+        partialImports.add(new ClientsPartialImport());
+        partialImports.add(new RolesPartialImport());
+        partialImports.add(new IdentityProvidersPartialImport());
+        partialImports.add(new UsersPartialImport());
+    }
+
+    public Response saveResources() {
+
+        PartialImportResults results = new PartialImportResults();
+
+        for (PartialImport partialImport : partialImports) {
+            try {
+                partialImport.prepare(rep, realm, session);
+            } catch (ErrorResponseException error) {
+                if (session.getTransaction().isActive()) session.getTransaction().setRollbackOnly();
+                return error.getResponse();
+            }
+        }
+
+        for (PartialImport partialImport : partialImports) {
+            try {
+                partialImport.removeOverwrites(realm, session);
+                results.addAllResults(partialImport.doImport(rep, realm, session));
+            } catch (ErrorResponseException error) {
+                if (session.getTransaction().isActive()) session.getTransaction().setRollbackOnly();
+                return error.getResponse();
+            }
+        }
+
+        for (PartialImportResult result : results.getResults()) {
+            switch (result.getAction()) {
+                case ADDED : addedEvent(result); break;
+                case OVERWRITTEN: overwrittenEvent(result); break;
+            }
+        }
+
+        if (session.getTransaction().isActive()) {
+            session.getTransaction().commit();
+        }
+
+        return Response.ok(results).build();
+    }
+
+    private void addedEvent(PartialImportResult result) {
+        adminEvent.operation(OperationType.CREATE)
+                  .resourcePath(result.getResourceType().getPath(), result.getId())
+                  .representation(result.getRepresentation())
+                  .success();
+    };
+
+    private void overwrittenEvent(PartialImportResult result) {
+        adminEvent.operation(OperationType.UPDATE)
+                  .resourcePath(result.getResourceType().getPath(), result.getId())
+                  .representation(result.getRepresentation())
+                  .success();
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/PartialImportResult.java b/services/src/main/java/org/keycloak/partialimport/PartialImportResult.java
new file mode 100644
index 0000000..6a732e8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/PartialImportResult.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.partialimport;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+
+/**
+ * This class represents a single result for a resource imported.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class PartialImportResult {
+
+    private final Action action;
+    private final ResourceType resourceType;
+    private final String resourceName;
+    private final String id;
+    private final Object representation;
+
+    private PartialImportResult(Action action, ResourceType resourceType, String resourceName, String id, Object representation) {
+        this.action = action;
+        this.resourceType = resourceType;
+        this.resourceName = resourceName;
+        this.id = id;
+        this.representation = representation;
+    };
+
+    public static PartialImportResult skipped(ResourceType resourceType, String resourceName, String id, Object representation) {
+        return new PartialImportResult(Action.SKIPPED, resourceType, resourceName, id, representation);
+    }
+
+    public static PartialImportResult added(ResourceType resourceType, String resourceName, String id, Object representation) {
+        return new PartialImportResult(Action.ADDED, resourceType, resourceName, id, representation);
+    }
+
+    public static PartialImportResult overwritten(ResourceType resourceType, String resourceName, String id, Object representation) {
+        return new PartialImportResult(Action.OVERWRITTEN, resourceType, resourceName, id, representation);
+    }
+
+    public Action getAction() {
+        return action;
+    }
+
+    public ResourceType getResourceType() {
+        return resourceType;
+    }
+
+    public String getResourceName() {
+        return resourceName;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    @JsonIgnore
+    public Object getRepresentation() {
+        return representation;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/PartialImportResults.java b/services/src/main/java/org/keycloak/partialimport/PartialImportResults.java
new file mode 100644
index 0000000..cc372f4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/PartialImportResults.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.partialimport;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Aggregates all the PartialImportResult objects.
+ * These results are used in the admin UI and for creating admin events.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class PartialImportResults {
+
+    private final Set<PartialImportResult> importResults = new HashSet<>();
+
+    public void addResult(PartialImportResult result) {
+        importResults.add(result);
+    }
+
+    public void addAllResults(PartialImportResults results) {
+        importResults.addAll(results.getResults());
+    }
+
+    public int getAdded() {
+        int added = 0;
+        for (PartialImportResult result : importResults) {
+            if (result.getAction() == Action.ADDED) added++;
+        }
+
+        return added;
+    }
+
+    public int getOverwritten() {
+        int overwritten = 0;
+        for (PartialImportResult result : importResults) {
+            if (result.getAction() == Action.OVERWRITTEN) overwritten++;
+        }
+
+        return overwritten;
+    }
+
+    public int getSkipped() {
+        int skipped = 0;
+        for (PartialImportResult result : importResults) {
+            if (result.getAction() == Action.SKIPPED) skipped++;
+        }
+
+        return skipped;
+    }
+
+    public Set<PartialImportResult> getResults() {
+        return importResults;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/RealmRolesPartialImport.java b/services/src/main/java/org/keycloak/partialimport/RealmRolesPartialImport.java
new file mode 100644
index 0000000..f7276a1
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/RealmRolesPartialImport.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.partialimport;
+
+import java.util.List;
+import java.util.Set;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.representations.idm.PartialImportRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.services.resources.admin.RoleResource;
+
+/**
+ * PartialImport handler for Realm Roles.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class RealmRolesPartialImport extends AbstractPartialImport<RoleRepresentation> {
+
+    public Set<RoleRepresentation> getToOverwrite() {
+        return this.toOverwrite;
+    }
+
+    public Set<RoleRepresentation> getToSkip() {
+        return this.toSkip;
+    }
+
+    @Override
+    public List<RoleRepresentation> getRepList(PartialImportRepresentation partialImportRep) {
+        if (partialImportRep.getRoles() == null) return null;
+        return partialImportRep.getRoles().getRealm();
+    }
+
+    @Override
+    public String getName(RoleRepresentation roleRep) {
+        if (roleRep.getName() == null)
+            throw new IllegalStateException("Realm role to import does not have a name");
+        return roleRep.getName();
+    }
+
+    @Override
+    public String getModelId(RealmModel realm, KeycloakSession session, RoleRepresentation roleRep) {
+        for (RoleModel role : realm.getRoles()) {
+            if (getName(roleRep).equals(role.getName())) return role.getId();
+        }
+
+        return null;
+    }
+
+    @Override
+    public boolean exists(RealmModel realm, KeycloakSession session, RoleRepresentation roleRep) {
+        for (RoleModel role : realm.getRoles()) {
+            if (getName(roleRep).equals(role.getName())) return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public String existsMessage(RoleRepresentation roleRep) {
+        return "Realm role '" + getName(roleRep) + "' already exists.";
+    }
+
+    @Override
+    public ResourceType getResourceType() {
+        return ResourceType.REALM_ROLE;
+    }
+
+    @Override
+    public void remove(RealmModel realm, KeycloakSession session, RoleRepresentation roleRep) {
+        RoleModel role = realm.getRole(getName(roleRep));
+        RoleHelper helper = new RoleHelper(realm);
+        helper.deleteRole(role);
+    }
+
+    @Override
+    public void create(RealmModel realm, KeycloakSession session, RoleRepresentation roleRep) {
+        realm.addRole(getName(roleRep));
+    }
+
+    public static class RoleHelper extends RoleResource {
+        public RoleHelper(RealmModel realm) {
+            super(realm);
+        }
+
+        @Override
+        protected void deleteRole(RoleModel role) {
+            super.deleteRole(role);
+        }
+    }
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/ResourceType.java b/services/src/main/java/org/keycloak/partialimport/ResourceType.java
new file mode 100644
index 0000000..25a09d2
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/ResourceType.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.partialimport;
+
+/**
+ * Enum for each resource type that can be partially imported.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public enum ResourceType {
+    USER, CLIENT, IDP, REALM_ROLE, CLIENT_ROLE;
+
+    /**
+     * Used to create the admin path in events.
+     *
+     * @return The resource portion of the path.
+     */
+    public String getPath() {
+        switch(this) {
+            case USER: return "users";
+            case CLIENT: return "clients";
+            case IDP: return "identity-provider-settings";
+            case REALM_ROLE: return "realms";
+            case CLIENT_ROLE: return "clients";
+            default: return "";
+        }
+    }
+
+    @Override
+    public String toString() {
+        switch(this) {
+            case USER: return "User";
+            case CLIENT: return "Client";
+            case IDP: return "Identity Provider";
+            case REALM_ROLE: return "Realm Role";
+            case CLIENT_ROLE: return "Client Role";
+            default: return super.toString();
+        }
+    }
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/RolesPartialImport.java b/services/src/main/java/org/keycloak/partialimport/RolesPartialImport.java
new file mode 100644
index 0000000..6bf145e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/RolesPartialImport.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.partialimport;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.ws.rs.core.Response;
+import org.jboss.logging.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.representations.idm.PartialImportRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.RolesRepresentation;
+import org.keycloak.services.ErrorResponse;
+
+/**
+ * This class handles both realm roles and client roles.  It delegates to
+ * RealmRolesPartialImport and ClientRolesPartialImport, which are no longer used
+ * directly by the PartialImportManager.
+ *
+ * The strategy is to utilize RepresentationToModel.importRoles().  That way,
+ * the complex code for bulk creation of roles is kept in one place.  To do this, the
+ * logic for skip needs to remove the roles that are going to be skipped so that
+ * importRoles() doesn't know about them.  The logic for overwrite needs to delete
+ * the overwritten roles before importRoles() is called.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class RolesPartialImport implements PartialImport<RolesRepresentation> {
+    protected static Logger logger = Logger.getLogger(RolesPartialImport.class);
+
+    private Set<RoleRepresentation> realmRolesToOverwrite;
+    private Set<RoleRepresentation> realmRolesToSkip;
+
+    private Map<String, Set<RoleRepresentation>> clientRolesToOverwrite;
+    private Map<String, Set<RoleRepresentation>> clientRolesToSkip;
+
+    private final RealmRolesPartialImport realmRolesPI = new RealmRolesPartialImport();
+    private final ClientRolesPartialImport clientRolesPI = new ClientRolesPartialImport();
+
+    @Override
+    public void prepare(PartialImportRepresentation rep, RealmModel realm, KeycloakSession session) throws ErrorResponseException {
+        prepareRealmRoles(rep, realm, session);
+        prepareClientRoles(rep, realm, session);
+    }
+
+    private void prepareRealmRoles(PartialImportRepresentation rep, RealmModel realm, KeycloakSession session) throws ErrorResponseException {
+        if (!rep.hasRealmRoles()) return;
+
+        realmRolesPI.prepare(rep, realm, session);
+        this.realmRolesToOverwrite = realmRolesPI.getToOverwrite();
+        this.realmRolesToSkip = realmRolesPI.getToSkip();
+    }
+
+    private void prepareClientRoles(PartialImportRepresentation rep, RealmModel realm, KeycloakSession session) throws ErrorResponseException {
+        if (!rep.hasClientRoles()) return;
+
+        clientRolesPI.prepare(rep, realm, session);
+        this.clientRolesToOverwrite = clientRolesPI.getToOverwrite();
+        this.clientRolesToSkip = clientRolesPI.getToSkip();
+    }
+
+    @Override
+    public void removeOverwrites(RealmModel realm, KeycloakSession session) {
+        deleteClientRoleOverwrites(realm);
+        deleteRealmRoleOverwrites(realm, session);
+    }
+
+    @Override
+    public PartialImportResults doImport(PartialImportRepresentation rep, RealmModel realm, KeycloakSession session) throws ErrorResponseException {
+        PartialImportResults results = new PartialImportResults();
+        if (!rep.hasRealmRoles() && !rep.hasClientRoles()) return results;
+
+        // finalize preparation and add results for skips
+        removeRealmRoleSkips(results, rep, realm, session);
+        removeClientRoleSkips(results, rep, realm);
+        if (rep.hasRealmRoles()) setUniqueIds(rep.getRoles().getRealm());
+        if (rep.hasClientRoles()) setUniqueIds(rep.getRoles().getClient());
+
+        try {
+            RepresentationToModel.importRoles(rep.getRoles(), realm);
+        } catch (Exception e) {
+            logger.error("Error importing roles", e);
+            throw new ErrorResponseException(ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR));
+        }
+
+        // add "add" results for new roles created
+        realmRoleAdds(results, rep, realm, session);
+        clientRoleAdds(results, rep, realm);
+
+        // add "overwritten" results for roles overwritten
+        addResultsForOverwrittenRealmRoles(results, realm, session);
+        addResultsForOverwrittenClientRoles(results, realm);
+
+        return results;
+    }
+
+    private void setUniqueIds(List<RoleRepresentation> realmRoles) {
+        for (RoleRepresentation realmRole : realmRoles) {
+            realmRole.setId(KeycloakModelUtils.generateId());
+        }
+    }
+
+    private void setUniqueIds(Map<String, List<RoleRepresentation>> clientRoles) {
+        for (String clientId : clientRoles.keySet()) {
+            for (RoleRepresentation clientRole : clientRoles.get(clientId)) {
+                clientRole.setId(KeycloakModelUtils.generateId());
+            }
+        }
+    }
+
+    private void removeRealmRoleSkips(PartialImportResults results,
+                                      PartialImportRepresentation rep,
+                                      RealmModel realm,
+                                      KeycloakSession session) {
+        if (isEmpty(realmRolesToSkip)) return;
+
+        for (RoleRepresentation roleRep : realmRolesToSkip) {
+            rep.getRoles().getRealm().remove(roleRep);
+            String modelId = realmRolesPI.getModelId(realm, session, roleRep);
+            results.addResult(realmRolesPI.skipped(modelId, roleRep));
+        }
+    }
+
+    private void removeClientRoleSkips(PartialImportResults results,
+                                       PartialImportRepresentation rep,
+                                       RealmModel realm) {
+        if (isEmpty(clientRolesToSkip)) return;
+
+        for (String clientId : clientRolesToSkip.keySet()) {
+            for (RoleRepresentation roleRep : clientRolesToSkip.get(clientId)) {
+                rep.getRoles().getClient().get(clientId).remove(roleRep);
+                String modelId = clientRolesPI.getModelId(realm, clientId);
+                results.addResult(clientRolesPI.skipped(clientId, modelId, roleRep));
+            }
+        }
+    }
+
+    private void deleteRealmRoleOverwrites(RealmModel realm, KeycloakSession session) {
+        if (isEmpty(realmRolesToOverwrite)) return;
+
+        for (RoleRepresentation roleRep : realmRolesToOverwrite) {
+            realmRolesPI.remove(realm, session, roleRep);
+        }
+    }
+
+    private void addResultsForOverwrittenRealmRoles(PartialImportResults results, RealmModel realm, KeycloakSession session) {
+        if (isEmpty(realmRolesToOverwrite)) return;
+
+        for (RoleRepresentation roleRep : realmRolesToOverwrite) {
+            String modelId = realmRolesPI.getModelId(realm, session, roleRep);
+            results.addResult(realmRolesPI.overwritten(modelId, roleRep));
+        }
+    }
+
+    private void deleteClientRoleOverwrites(RealmModel realm) {
+        if (isEmpty(clientRolesToOverwrite)) return;
+
+        for (String clientId : clientRolesToOverwrite.keySet()) {
+            for (RoleRepresentation roleRep : clientRolesToOverwrite.get(clientId)) {
+                clientRolesPI.deleteRole(realm, clientId, roleRep);
+            }
+        }
+    }
+
+    private void addResultsForOverwrittenClientRoles(PartialImportResults results, RealmModel realm) {
+        if (isEmpty(clientRolesToOverwrite)) return;
+
+        for (String clientId : clientRolesToOverwrite.keySet()) {
+            for (RoleRepresentation roleRep : clientRolesToOverwrite.get(clientId)) {
+                String modelId = clientRolesPI.getModelId(realm, clientId);
+                results.addResult(clientRolesPI.overwritten(clientId, modelId, roleRep));
+            }
+        }
+    }
+
+    private boolean isEmpty(Set set) {
+        return (set == null) || (set.isEmpty());
+    }
+
+    private boolean isEmpty(Map map) {
+        return (map == null) || (map.isEmpty());
+    }
+
+    private void realmRoleAdds(PartialImportResults results,
+                               PartialImportRepresentation rep,
+                               RealmModel realm,
+                               KeycloakSession session) {
+        if (!rep.hasRealmRoles()) return;
+
+        for (RoleRepresentation roleRep : rep.getRoles().getRealm()) {
+            if (realmRolesToOverwrite.contains(roleRep)) continue;
+            if (realmRolesToSkip.contains(roleRep)) continue;
+
+            String modelId = realmRolesPI.getModelId(realm, session, roleRep);
+            results.addResult(realmRolesPI.added(modelId, roleRep));
+        }
+    }
+
+    private void clientRoleAdds(PartialImportResults results,
+                                PartialImportRepresentation rep,
+                                RealmModel realm) {
+        if (!rep.hasClientRoles()) return;
+
+        Map<String, List<RoleRepresentation>> repList = clientRolesPI.getRepList(rep);
+        for (String clientId : repList.keySet()) {
+            for (RoleRepresentation roleRep : repList.get(clientId)) {
+                if (clientRolesToOverwrite.get(clientId).contains(roleRep)) continue;
+                if (clientRolesToSkip.get(clientId).contains(roleRep)) continue;
+
+                String modelId = clientRolesPI.getModelId(realm, clientId);
+                results.addResult(clientRolesPI.added(clientId, modelId, roleRep));
+            }
+        }
+    }
+}
diff --git a/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java b/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java
new file mode 100644
index 0000000..2ae3fc3
--- /dev/null
+++ b/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.partialimport;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.representations.idm.PartialImportRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.services.managers.UserManager;
+
+/**
+ * PartialImport handler for users.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class UsersPartialImport extends AbstractPartialImport<UserRepresentation> {
+
+    // Sometimes session.users().getUserByUsername() doesn't work right after create,
+    // so we cache the created id here.
+    private final Map<String, String> createdIds = new HashMap<>();
+
+    @Override
+    public List<UserRepresentation> getRepList(PartialImportRepresentation partialImportRep) {
+        return partialImportRep.getUsers();
+    }
+
+    @Override
+    public String getName(UserRepresentation user) {
+        if (user.getUsername() != null) return user.getUsername();
+
+        return user.getEmail();
+    }
+
+    @Override
+    public String getModelId(RealmModel realm, KeycloakSession session, UserRepresentation user) {
+        if (createdIds.containsKey(getName(user))) return createdIds.get(getName(user));
+
+        String userName = user.getUsername();
+        if (userName != null) {
+            return session.users().getUserByUsername(userName, realm).getId();
+        } else {
+            String email = user.getEmail();
+            return session.users().getUserByEmail(email, realm).getId();
+        }
+    }
+
+    @Override
+    public boolean exists(RealmModel realm, KeycloakSession session, UserRepresentation user) {
+        return userNameExists(realm, session, user) || userEmailExists(realm, session, user);
+    }
+
+    private boolean userNameExists(RealmModel realm, KeycloakSession session, UserRepresentation user) {
+        return session.users().getUserByUsername(user.getUsername(), realm) != null;
+    }
+
+    private boolean userEmailExists(RealmModel realm, KeycloakSession session, UserRepresentation user) {
+        return (user.getEmail() != null) &&
+               (session.users().getUserByEmail(user.getEmail(), realm) != null);
+    }
+
+    @Override
+    public String existsMessage(UserRepresentation user) {
+        if (user.getEmail() == null) {
+            return "User with user name " + getName(user) + " already exists.";
+        }
+
+        return "User with user name " + getName(user) + " or with email " + user.getEmail() + " already exists.";
+    }
+
+    @Override
+    public ResourceType getResourceType() {
+        return ResourceType.USER;
+    }
+
+    @Override
+    public void remove(RealmModel realm, KeycloakSession session, UserRepresentation user) {
+        UserModel userModel = session.users().getUserByUsername(user.getUsername(), realm);
+        if (userModel == null) {
+            userModel = session.users().getUserByEmail(user.getEmail(), realm);
+        }
+
+        boolean success = new UserManager(session).removeUser(realm, userModel);
+        if (!success) throw new RuntimeException("Unable to overwrite user " + getName(user));
+    }
+
+    @Override
+    public void create(RealmModel realm, KeycloakSession session, UserRepresentation user) {
+        Map<String, ClientModel> apps = realm.getClientNameMap();
+        user.setId(KeycloakModelUtils.generateId());
+        UserModel userModel = RepresentationToModel.createUser(session, realm, user, apps);
+        if (userModel == null) throw new RuntimeException("Unable to create user " + getName(user));
+        createdIds.put(getName(user), userModel.getId());
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java
index 637218e..ea88a7d 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java
@@ -21,7 +21,7 @@ import org.keycloak.common.util.Time;
 import javax.ws.rs.core.UriInfo;
 
 public class AdminEventBuilder {
-    
+
     private static final Logger log = Logger.getLogger(AdminEventBuilder.class);
 
     private EventStoreProvider store;
@@ -59,17 +59,17 @@ public class AdminEventBuilder {
         authUser(auth.getUser());
         authIpAddress(clientConnection.getRemoteAddr());
     }
-    
+
     public AdminEventBuilder realm(RealmModel realm) {
         adminEvent.setRealmId(realm.getId());
         return this;
     }
-    
+
     public AdminEventBuilder realm(String realmId) {
         adminEvent.setRealmId(realmId);
         return this;
     }
-    
+
     public AdminEventBuilder operation(OperationType e) {
         adminEvent.setOperationType(e);
         return this;
@@ -123,6 +123,18 @@ public class AdminEventBuilder {
         return this;
     }
 
+    public AdminEventBuilder resourcePath(String... pathElements) {
+        StringBuilder sb = new StringBuilder();
+        for (String element : pathElements) {
+            sb.append("/");
+            sb.append(element);
+        }
+        if (pathElements.length > 0) sb.deleteCharAt(0); // remove leading '/'
+
+        adminEvent.setResourcePath(sb.toString());
+        return this;
+    }
+
     public AdminEventBuilder resourcePath(UriInfo uriInfo) {
         String path = getResourcePath(uriInfo);
         adminEvent.setResourcePath(path);
@@ -155,7 +167,7 @@ public class AdminEventBuilder {
         adminEvent.setError(error);
         send();
     }
-    
+
     public AdminEventBuilder representation(Object value) {
         if (value == null || value.equals("")) {
             return this;
@@ -167,7 +179,7 @@ public class AdminEventBuilder {
         }
         return this;
     }
-    
+
     public AdminEvent getEvent() {
         return adminEvent;
     }
@@ -190,7 +202,7 @@ public class AdminEventBuilder {
                 log.error("Failed to save event", t);
             }
         }
-        
+
         if (listeners != null) {
             for (EventListenerProvider l : listeners) {
                 try {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 7c19f61..6a26b85 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -68,7 +68,7 @@ public class ClientResource {
     private AdminEventBuilder adminEvent;
     protected ClientModel client;
     protected KeycloakSession session;
-    
+
     @Context
     protected UriInfo uriInfo;
 
@@ -107,11 +107,7 @@ public class ClientResource {
         auth.requireManage();
 
         try {
-            if (TRUE.equals(rep.isServiceAccountsEnabled()) && !client.isServiceAccountsEnabled()) {
-                new ClientManager(new RealmManager(session)).enableServiceAccount(client);;
-            }
-
-            RepresentationToModel.updateClient(rep, client);
+            updateClientFromRep(rep, client, session);
             adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
             return Response.noContent().build();
         } catch (ModelDuplicateException e) {
@@ -119,6 +115,13 @@ public class ClientResource {
         }
     }
 
+    public static void updateClientFromRep(ClientRepresentation rep, ClientModel client, KeycloakSession session) throws ModelDuplicateException {
+        if (TRUE.equals(rep.isServiceAccountsEnabled()) && !client.isServiceAccountsEnabled()) {
+            new ClientManager(new RealmManager(session)).enableServiceAccount(client);
+        }
+
+        RepresentationToModel.updateClient(rep, client);
+    }
 
     /**
      * Get representation of the client
@@ -381,9 +384,9 @@ public class ClientResource {
         auth.requireManage();
         adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
         return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client);
-    
+
     }
-    
+
     /**
      * Get application session count
      *
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
index b49cf91..f824a0e 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
@@ -58,7 +58,7 @@ public class IdentityProviderResource {
     private final KeycloakSession session;
     private final IdentityProviderModel identityProviderModel;
     private final AdminEventBuilder adminEvent;
-    
+
     @Context private UriInfo uriInfo;
 
     public IdentityProviderResource(RealmAuth auth, RealmModel realm, KeycloakSession session, IdentityProviderModel identityProviderModel, AdminEventBuilder adminEvent) {
@@ -94,9 +94,9 @@ public class IdentityProviderResource {
         this.auth.requireManage();
 
         this.realm.removeIdentityProviderByAlias(this.identityProviderModel.getAlias());
-        
+
         adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
-        
+
         return Response.noContent().build();
     }
 
@@ -113,30 +113,34 @@ public class IdentityProviderResource {
         try {
             this.auth.requireManage();
 
-            String internalId = providerRep.getInternalId();
-            String newProviderId = providerRep.getAlias();
-            String oldProviderId = getProviderIdByInternalId(this.realm, internalId);
-
-            this.realm.updateIdentityProvider(RepresentationToModel.toModel(realm, providerRep));
-
-            if (oldProviderId != null && !oldProviderId.equals(newProviderId)) {
+            updateIdpFromRep(providerRep, realm, session);
 
-                // Admin changed the ID (alias) of identity provider. We must update all clients and users
-                logger.debug("Changing providerId in all clients and linked users. oldProviderId=" + oldProviderId + ", newProviderId=" + newProviderId);
-
-                updateUsersAfterProviderAliasChange(this.session.users().getUsers(this.realm, false), oldProviderId, newProviderId);
-            }
-            
             adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(providerRep).success();
-            
+
             return Response.noContent().build();
         } catch (ModelDuplicateException e) {
             return ErrorResponse.exists("Identity Provider " + providerRep.getAlias() + " already exists");
         }
     }
 
+    public static void updateIdpFromRep(IdentityProviderRepresentation providerRep, RealmModel realm, KeycloakSession session) {
+        String internalId = providerRep.getInternalId();
+        String newProviderId = providerRep.getAlias();
+        String oldProviderId = getProviderIdByInternalId(realm, internalId);
+
+        realm.updateIdentityProvider(RepresentationToModel.toModel(realm, providerRep));
+
+        if (oldProviderId != null && !oldProviderId.equals(newProviderId)) {
+
+            // Admin changed the ID (alias) of identity provider. We must update all clients and users
+            logger.debug("Changing providerId in all clients and linked users. oldProviderId=" + oldProviderId + ", newProviderId=" + newProviderId);
+
+            updateUsersAfterProviderAliasChange(session.users().getUsers(realm, false), oldProviderId, newProviderId, realm, session);
+        }
+    }
+
     // return ID of IdentityProvider from realm based on internalId of this provider
-    private String getProviderIdByInternalId(RealmModel realm, String providerInternalId) {
+    private static String getProviderIdByInternalId(RealmModel realm, String providerInternalId) {
         List<IdentityProviderModel> providerModels = realm.getIdentityProviders();
         for (IdentityProviderModel providerModel : providerModels) {
             if (providerModel.getInternalId().equals(providerInternalId)) {
@@ -147,17 +151,17 @@ public class IdentityProviderResource {
         return null;
     }
 
-    private void updateUsersAfterProviderAliasChange(List<UserModel> users, String oldProviderId, String newProviderId) {
+    private static void updateUsersAfterProviderAliasChange(List<UserModel> users, String oldProviderId, String newProviderId, RealmModel realm, KeycloakSession session) {
         for (UserModel user : users) {
-            FederatedIdentityModel federatedIdentity = this.session.users().getFederatedIdentity(user, oldProviderId, this.realm);
+            FederatedIdentityModel federatedIdentity = session.users().getFederatedIdentity(user, oldProviderId, realm);
             if (federatedIdentity != null) {
                 // Remove old link first
-                this.session.users().removeFederatedIdentity(this.realm, user, oldProviderId);
+                session.users().removeFederatedIdentity(realm, user, oldProviderId);
 
                 // And create new
                 FederatedIdentityModel newFederatedIdentity = new FederatedIdentityModel(newProviderId, federatedIdentity.getUserId(), federatedIdentity.getUserName(),
                         federatedIdentity.getToken());
-                this.session.users().addFederatedIdentity(this.realm, user, newFederatedIdentity);
+                session.users().addFederatedIdentity(realm, user, newFederatedIdentity);
             }
         }
     }
@@ -263,10 +267,10 @@ public class IdentityProviderResource {
         auth.requireManage();
         IdentityProviderMapperModel model = RepresentationToModel.toModel(mapper);
         model = realm.addIdentityProviderMapper(model);
-        
+
         adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId())
             .representation(mapper).success();
-        
+
         return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
 
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index cb97855..1cece17 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -66,6 +66,8 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.PatternSyntaxException;
+import org.keycloak.partialimport.PartialImportManager;
+import org.keycloak.representations.idm.PartialImportRepresentation;
 
 /**
  * Base resource class for the admin REST api of one realm
@@ -241,7 +243,7 @@ public class RealmAdminResource {
             for (final UserFederationProviderModel fedProvider : federationProviders) {
                 usersSyncManager.refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, realm.getId());
             }
-            
+
             adminEvent.operation(OperationType.UPDATE).representation(rep).success();
             return Response.noContent().build();
         } catch (PatternSyntaxException e) {
@@ -466,7 +468,7 @@ public class RealmAdminResource {
         if (user != null) {
             query.user(user);
         }
-        
+
         if(dateFrom != null) {
             SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
             Date from = null;
@@ -477,7 +479,7 @@ public class RealmAdminResource {
             }
             query.fromDate(from);
         }
-        
+
         if(dateTo != null) {
             SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
             Date to = null;
@@ -501,7 +503,7 @@ public class RealmAdminResource {
 
         return query.getResultList();
     }
-    
+
     /**
      * Get admin events
      *
@@ -540,15 +542,15 @@ public class RealmAdminResource {
         if (authClient != null) {
             query.authClient(authClient);
         }
-        
+
         if (authUser != null) {
             query.authUser(authUser);
         }
-        
+
         if (authIpAddress != null) {
             query.authIpAddress(authIpAddress);
         }
-        
+
         if (resourcePath != null) {
             query.resourcePath(resourcePath);
         }
@@ -561,7 +563,7 @@ public class RealmAdminResource {
             }
             query.operation(t);
         }
-        
+
         if(dateFrom != null) {
             SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
             Date from = null;
@@ -572,7 +574,7 @@ public class RealmAdminResource {
             }
             query.fromTime(from);
         }
-        
+
         if(dateTo != null) {
             SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
             Date to = null;
@@ -606,7 +608,7 @@ public class RealmAdminResource {
         EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
         eventStore.clear(realm.getId());
     }
-    
+
     /**
      * Delete all admin events
      *
@@ -709,5 +711,18 @@ public class RealmAdminResource {
         return ModelToRepresentation.toGroupHierarchy(found, true);
     }
 
-
+    /**
+     * Partial import from a JSON file to an existing realm.
+     *
+     * @param rep
+     * @return
+     */
+    @Path("partialImport")
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response partialImport(PartialImportRepresentation rep) {
+        auth.requireManage();
+        PartialImportManager partialImport = new PartialImportManager(rep, session, realm, adminEvent);
+        return partialImport.saveResources();
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 67bd67e..f78f33f 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -150,7 +150,7 @@ public class UsersResource {
                 }
             }
 
-            updateUserFromRep(user, rep, attrsToRemove);
+            updateUserFromRep(user, rep, attrsToRemove, realm, session);
             adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
 
             if (session.getTransaction().isActive()) {
@@ -189,7 +189,7 @@ public class UsersResource {
         try {
             UserModel user = session.users().addUser(realm, rep.getUsername());
             Set<String> emptySet = Collections.emptySet();
-            updateUserFromRep(user, rep, emptySet);
+            updateUserFromRep(user, rep, emptySet, realm, session);
 
             adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, user.getId()).representation(rep).success();
 
@@ -206,7 +206,7 @@ public class UsersResource {
         }
     }
 
-    private void updateUserFromRep(UserModel user, UserRepresentation rep, Set<String> attrsToRemove) {
+    public static void updateUserFromRep(UserModel user, UserRepresentation rep, Set<String> attrsToRemove, RealmModel realm, KeycloakSession session) {
         if (realm.isEditUsernameAllowed()) {
             user.setUsername(rep.getUsername());
         }