org.wso2.carbon.identity.scim.provider.impl.SCIMUserManager.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.identity.scim.provider.impl.SCIMUserManager.java

Source

/*
 * Copyright (c) 2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you 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.wso2.carbon.identity.scim.provider.impl;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException;
import org.wso2.carbon.identity.application.common.model.ProvisioningServiceProviderType;
import org.wso2.carbon.identity.application.common.model.ServiceProvider;
import org.wso2.carbon.identity.application.common.model.ThreadLocalProvisioningServiceProvider;
import org.wso2.carbon.identity.application.common.util.IdentityApplicationManagementUtil;
import org.wso2.carbon.identity.application.mgt.ApplicationConstants;
import org.wso2.carbon.identity.application.mgt.ApplicationManagementService;
import org.wso2.carbon.identity.provisioning.IdentityProvisioningConstants;
import org.wso2.carbon.identity.provisioning.IdentityProvisioningException;
import org.wso2.carbon.identity.provisioning.OutboundProvisioningManager;
import org.wso2.carbon.identity.provisioning.ProvisioningEntity;
import org.wso2.carbon.identity.provisioning.ProvisioningEntityType;
import org.wso2.carbon.identity.provisioning.ProvisioningOperation;
import org.wso2.carbon.identity.provisioning.listener.DefaultInboundUserProvisioningListener;
import org.wso2.carbon.identity.scim.common.group.SCIMGroupHandler;
import org.wso2.carbon.identity.scim.common.utils.AttributeMapper;
import org.wso2.carbon.identity.scim.common.utils.IdentitySCIMException;
import org.wso2.carbon.identity.scim.common.utils.SCIMCommonConstants;
import org.wso2.carbon.identity.scim.common.utils.SCIMCommonUtils;
import org.wso2.carbon.user.api.ClaimMapping;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.core.UserCoreConstants;
import org.wso2.carbon.user.core.UserStoreManager;
import org.wso2.carbon.user.core.claim.ClaimManager;
import org.wso2.carbon.user.core.util.UserCoreUtil;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import org.wso2.charon.core.attributes.Attribute;
import org.wso2.charon.core.attributes.SimpleAttribute;
import org.wso2.charon.core.exceptions.CharonException;
import org.wso2.charon.core.exceptions.DuplicateResourceException;
import org.wso2.charon.core.exceptions.NotFoundException;
import org.wso2.charon.core.extensions.UserManager;
import org.wso2.charon.core.objects.AbstractSCIMObject;
import org.wso2.charon.core.objects.Group;
import org.wso2.charon.core.objects.SCIMObject;
import org.wso2.charon.core.objects.User;
import org.wso2.charon.core.schema.SCIMConstants;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class SCIMUserManager implements UserManager {
    public static final String USER_NAME_STRING = "userName";
    private static Log log = LogFactory.getLog(SCIMUserManager.class);
    private UserStoreManager carbonUM = null;
    private ClaimManager carbonClaimManager = null;
    private String consumerName;

    public SCIMUserManager(UserStoreManager carbonUserStoreManager, String userName, ClaimManager claimManager) {
        carbonUM = carbonUserStoreManager;
        consumerName = userName;
        carbonClaimManager = claimManager;
    }

    @Override
    public User createUser(User user) throws CharonException, DuplicateResourceException {
        return createUser(user, false);
    }

    @Override
    public User createUser(User user, boolean isBulkUserAdd) throws CharonException, DuplicateResourceException {

        try {

            ThreadLocalProvisioningServiceProvider threadLocalSP = IdentityApplicationManagementUtil
                    .getThreadLocalProvisioningServiceProvider();
            //isBulkUserAdd is true indicates bulk user add
            if (isBulkUserAdd) {
                threadLocalSP.setBulkUserAdd(true);
            }

            String userStoreName = null;

            if (!UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME.equalsIgnoreCase(getUserStoreDomainFromSP())) {
                userStoreName = getUserStoreDomainFromSP();
            }

            StringBuilder userName = new StringBuilder();

            if (userStoreName != null && userStoreName.trim().length() > 0) {
                // if we have set a user store under provisioning configuration - we should only use
                // that.
                String currentUserName = user.getUserName();
                currentUserName = UserCoreUtil.removeDomainFromName(currentUserName);
                user.setUserName(userName.append(userStoreName).append(CarbonConstants.DOMAIN_SEPARATOR)
                        .append(currentUserName).toString());
            }

        } catch (IdentityApplicationManagementException e) {
            throw new CharonException("Error retrieving User Store name. ", e);
        }

        try {
            //TODO: Start tenant flow at the scim authentication point
            PrivilegedCarbonContext.startTenantFlow();
            PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
            carbonContext.setTenantDomain(MultitenantUtils.getTenantDomain(consumerName));
            carbonContext.getTenantId(true);
            carbonContext.setUsername(consumerName);

            //if operating in dumb mode, do not persist the operation, only provision to providers

            //Persist in carbon user store
            if (log.isDebugEnabled()) {
                log.debug("Creating user: " + user.getUserName());
            }
            /*set thread local property to signal the downstream SCIMUserOperationListener
            about the provisioning route.*/
            SCIMCommonUtils.setThreadLocalIsManagedThroughSCIMEP(true);
            Map<String, String> claimsMap = AttributeMapper.getClaimsMap(user);

            /*skip groups attribute since we map groups attribute to actual groups in ldap.
            and do not update it as an attribute in user schema*/
            if (claimsMap.containsKey(SCIMConstants.GROUPS_URI)) {
                claimsMap.remove(SCIMConstants.GROUPS_URI);
            }

            //TODO: Do not accept the roles list - it is read only.
            if (carbonUM.isExistingUser(user.getUserName())) {
                String error = "User with the name: " + user.getUserName() + " already exists in the system.";
                throw new DuplicateResourceException(error);
            }
            if (claimsMap.containsKey(SCIMConstants.USER_NAME_URI)) {
                claimsMap.remove(SCIMConstants.USER_NAME_URI);
            }
            carbonUM.addUser(user.getUserName(), user.getPassword(), null, claimsMap, null);
            log.info("User: " + user.getUserName() + " is created through SCIM.");

        } catch (UserStoreException e) {
            String errMsg = "Error in adding the user: " + user.getUserName() + " to the user store. ";
            errMsg += e.getMessage();
            throw new CharonException(errMsg, e);
        } finally {
            PrivilegedCarbonContext.endTenantFlow();
        }
        return user;

    }

    @Override
    public User getUser(String userId) throws CharonException {
        if (log.isDebugEnabled()) {
            log.debug("Retrieving user: " + userId);
        }
        User scimUser = null;
        try {
            ClaimMapping[] claims;
            //get the user name of the user with this id
            String[] userNames = carbonUM.getUserList(SCIMConstants.ID_URI, userId,
                    UserCoreConstants.DEFAULT_PROFILE);

            if (userNames == null || userNames.length == 0) {
                if (log.isDebugEnabled()) {
                    log.debug("User with SCIM id: " + userId + " does not exist in the system.");
                }
                return null;
            } else if (userNames != null && userNames.length == 0) {
                if (log.isDebugEnabled()) {
                    log.debug("User with SCIM id: " + userId + " does not exist in the system.");
                }
                return null;
            } else {
                //get claims related to SCIM claim dialect
                claims = carbonClaimManager.getAllClaimMappings(SCIMCommonConstants.SCIM_CLAIM_DIALECT);
                List<String> claimURIList = new ArrayList<>();
                for (ClaimMapping claim : claims) {
                    claimURIList.add(claim.getClaim().getClaimUri());
                }
                //we assume (since id is unique per user) only one user exists for a given id
                scimUser = this.getSCIMUser(userNames[0], claimURIList);

                log.info("User: " + scimUser.getUserName() + " is retrieved through SCIM.");
            }

        } catch (UserStoreException e) {
            throw new CharonException(
                    "Error in getting user information from Carbon User Store for" + "user: " + userId, e);
        }
        return scimUser;
    }

    @Override
    public List<User> listUsers() throws CharonException {

        ClaimMapping[] claims;
        List<User> users = new ArrayList<>();
        try {
            String[] userNames = carbonUM.getUserList(SCIMConstants.ID_URI, "*", null);
            if (userNames != null && userNames.length != 0) {
                //get claims related to SCIM claim dialect
                claims = carbonClaimManager.getAllClaimMappings(SCIMCommonConstants.SCIM_CLAIM_DIALECT);
                List<String> claimURIList = new ArrayList<>();
                for (ClaimMapping claim : claims) {
                    claimURIList.add(claim.getClaim().getClaimUri());
                }
                for (String userName : userNames) {
                    if (userName.contains(UserCoreConstants.NAME_COMBINER)) {
                        userName = userName.split("\\" + UserCoreConstants.NAME_COMBINER)[0];
                    }
                    User scimUser = this.getSCIMMetaUser(userName);
                    Map<String, Attribute> attrMap = scimUser.getAttributeList();
                    if (attrMap != null && !attrMap.isEmpty()) {
                        users.add(scimUser);
                    }
                }
            }
        } catch (UserStoreException e) {
            throw new CharonException("Error while retrieving users from user store..", e);
        }
        return users;
    }

    @Override
    public List<User> listUsersByAttribute(Attribute attribute) {
        return Collections.emptyList();
    }

    @Override
    public List<User> listUsersByFilter(String attributeName, String filterOperation, String attributeValue)
            throws CharonException {
        //since we only support eq filter operation at the moment, no need to check for that.
        if (log.isDebugEnabled()) {
            log.debug("Listing users by filter: " + attributeName + filterOperation + attributeValue);
        }
        List<User> filteredUsers = new ArrayList<>();
        ClaimMapping[] claims;
        User scimUser = null;
        try {
            String[] userNames = null;
            if (!SCIMConstants.GROUPS_URI.equals(attributeName)) {
                //get the user name of the user with this id
                userNames = carbonUM.getUserList(attributeName, attributeValue, UserCoreConstants.DEFAULT_PROFILE);
            } else {
                userNames = carbonUM.getUserListOfRole(attributeValue);
            }

            if (userNames == null || userNames.length == 0) {
                if (log.isDebugEnabled()) {
                    log.debug("Users with filter: " + attributeName + filterOperation + attributeValue
                            + " does not exist in the system.");
                }
                return Collections.emptyList();
            } else {
                //get claims related to SCIM claim dialect
                claims = carbonClaimManager.getAllClaimMappings(SCIMCommonConstants.SCIM_CLAIM_DIALECT);
                List<String> claimURIList = new ArrayList<>();
                for (ClaimMapping claim : claims) {
                    claimURIList.add(claim.getClaim().getClaimUri());
                }
                for (String userName : userNames) {

                    if (CarbonConstants.REGISTRY_ANONNYMOUS_USERNAME.equals(userName)) {
                        continue;
                    }

                    scimUser = this.getSCIMMetaUser(userName);
                    //if SCIM-ID is not present in the attributes, skip
                    if (scimUser.getId() == null) {
                        continue;
                    }
                    filteredUsers.add(scimUser);
                }
                log.info("Users filtered through SCIM for the filter: " + attributeName + filterOperation
                        + attributeValue);
            }

        } catch (UserStoreException e) {
            throw new CharonException("Error in filtering users by attribute name : " + attributeName + ", "
                    + "attribute value : " + attributeValue + " and filter operation " + filterOperation, e);
        }
        return filteredUsers;
    }

    @Override
    public List<User> listUsersBySort(String s, String s1) {
        return Collections.emptyList();
    }

    @Override
    public List<User> listUsersWithPagination(int i, int i1) {
        return Collections.emptyList();
    }

    @Override
    public User updateUser(User user) throws CharonException {

        if (log.isDebugEnabled()) {
            log.debug("Updating user: " + user.getUserName());
        }
        try {
            /*set thread local property to signal the downstream SCIMUserOperationListener
            about the provisioning route.*/
            SCIMCommonUtils.setThreadLocalIsManagedThroughSCIMEP(true);
            //get user claim values
            Map<String, String> claims = AttributeMapper.getClaimsMap(user);

            //check if username of the updating user existing in the userstore.
            //TODO:immutable userId can be something else other than username. eg: mail.
            //Therefore, correct way is to check the corresponding SCIM attribute for the
            //UserNameAttribute of user-mgt.xml.
            // Refer: SCIMUserOperationListener#isProvisioningActionAuthorized method.
            try {
                String userStoreDomainFromSP = getUserStoreDomainFromSP();
                User oldUser = this.getUser(user.getId());
                if (userStoreDomainFromSP != null && !userStoreDomainFromSP
                        .equalsIgnoreCase(UserCoreUtil.extractDomainFromName(oldUser.getUserName()))) {
                    throw new CharonException("User :" + oldUser.getUserName() + "is not belong to user store "
                            + userStoreDomainFromSP + "Hence user updating fail");
                }
                if (getUserStoreDomainFromSP() != null && !UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME
                        .equalsIgnoreCase(getUserStoreDomainFromSP())) {
                    user.setUserName(UserCoreUtil.addDomainToName(
                            UserCoreUtil.removeDomainFromName(user.getUserName()), getUserStoreDomainFromSP()));
                }
            } catch (IdentityApplicationManagementException e) {
                throw new CharonException("Error retrieving User Store name. ", e);
            }
            if (!carbonUM.isExistingUser(user.getUserName())) {
                throw new CharonException("User name is immutable in carbon user store.");
            }

            /*skip groups attribute since we map groups attribute to actual groups in ldap.
            and do not update it as an attribute in user schema*/
            if (claims.containsKey(SCIMConstants.GROUPS_URI)) {
                claims.remove(SCIMConstants.GROUPS_URI);
            }

            if (claims.containsKey(SCIMConstants.USER_NAME_URI)) {
                claims.remove(SCIMConstants.USER_NAME_URI);
            }

            ClaimMapping[] claimList;
            claimList = carbonClaimManager.getAllClaimMappings(SCIMCommonConstants.SCIM_CLAIM_DIALECT);
            List<String> claimURIList = new ArrayList<>();
            for (ClaimMapping claim : claimList) {
                claimURIList.add(claim.getClaim().getClaimUri());
            }

            Map<String, String> oldClaimList = carbonUM.getUserClaimValues(user.getUserName(),
                    claimURIList.toArray(new String[claimURIList.size()]), null);

            for (Map.Entry<String, String> entry : oldClaimList.entrySet()) {
                if (!entry.getKey().equals(SCIMConstants.ID_URI)
                        && !entry.getKey().equals(SCIMConstants.USER_NAME_URI)
                        && !entry.getKey().equals(SCIMConstants.META_CREATED_URI)
                        && !entry.getKey().equals(SCIMConstants.META_LAST_MODIFIED_URI)
                        && !entry.getKey().equals(SCIMConstants.META_LOCATION_URI)
                        && !entry.getKey().equals(SCIMConstants.NAME_FAMILY_NAME_URI)) {
                    carbonUM.deleteUserClaimValue(user.getUserName(), entry.getKey(), null);
                }
            }

            //set user claim values
            carbonUM.setUserClaimValues(user.getUserName(), claims, null);
            //if password is updated, set it separately
            if (user.getPassword() != null) {
                carbonUM.updateCredentialByAdmin(user.getUserName(), user.getPassword());
            }
            log.info("User: " + user.getUserName() + " updated updated through SCIM.");
        } catch (UserStoreException e) {
            throw new CharonException("Error while updating attributes of user: " + user.getUserName(), e);
        }

        return user;
    }

    @Override
    public User patchUser(User newUser, User oldUser, String[] attributesToDelete) throws CharonException {

        if (log.isDebugEnabled()) {
            log.debug("Updating user: " + newUser.getUserName());
        }
        try {
            String userStoreDomainFromSP = getUserStoreDomainFromSP();
            if (userStoreDomainFromSP != null && !userStoreDomainFromSP
                    .equalsIgnoreCase(UserCoreUtil.extractDomainFromName(oldUser.getUserName()))) {
                throw new CharonException("User :" + oldUser.getUserName() + "is not belong to user store "
                        + userStoreDomainFromSP + "Hence user updating fail");
            }
        } catch (IdentityApplicationManagementException e) {
            throw new CharonException("Error retrieving User Store name. ", e);
        }
        try {
            /*set thread local property to signal the downstream SCIMUserOperationListener
            about the provisioning route.*/
            SCIMCommonUtils.setThreadLocalIsManagedThroughSCIMEP(true);
            //get user claim values
            Map<String, String> claims = AttributeMapper.getClaimsMap(newUser);
            if (UserCoreUtil.extractDomainFromName(newUser.getUserName())
                    .equals(UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME)
                    && !(UserCoreUtil.extractDomainFromName(oldUser.getUserName())
                            .equals(UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME))) {
                newUser.setUserName(UserCoreUtil.addDomainToName(newUser.getUserName(),
                        UserCoreUtil.extractDomainFromName(oldUser.getUserName())));
            }

            //check if username of the updating user existing in the userStore.
            if (!carbonUM.isExistingUser(newUser.getUserName())) {
                throw new CharonException("User name is immutable in carbon user store.");
            }

            /*skip groups attribute since we map groups attribute to actual groups in ldap.
            and do not update it as an attribute in user schema*/
            if (claims.containsKey(SCIMConstants.GROUPS_URI)) {
                claims.remove(SCIMConstants.GROUPS_URI);
            }

            if (claims.containsKey(SCIMConstants.USER_NAME_URI)) {
                claims.remove(SCIMConstants.USER_NAME_URI);
            }

            for (int i = 0; i < attributesToDelete.length; i++) {
                attributesToDelete[i] = SCIMConstants.CORE_SCHEMA_URI + ":" + attributesToDelete[i];
            }
            carbonUM.deleteUserClaimValues(oldUser.getUserName(), attributesToDelete, null);

            //set user claim values
            carbonUM.setUserClaimValues(oldUser.getUserName(), claims, null);
            //if password is updated, set it separately
            if (StringUtils.isNotEmpty(newUser.getPassword())) {
                carbonUM.updateCredentialByAdmin(newUser.getUserName(), newUser.getPassword());
            }
            log.info("User: " + newUser.getUserName() + " updated updated through SCIM.");
        } catch (org.wso2.carbon.user.core.UserStoreException e) {
            throw new CharonException("Error while updating attributes of user: " + newUser.getUserName(), e);
        }

        return newUser;
    }

    public User updateUser(List<Attribute> attributes) {
        return null;
    }

    @Override
    public void deleteUser(String userId) throws NotFoundException, CharonException {

        if (log.isDebugEnabled()) {
            log.debug("Deleting user: " + userId);
        }
        //get the user name of the user with this id
        String[] userNames = null;
        String userName = null;
        try {
            /*set thread local property to signal the downstream SCIMUserOperationListener
            about the provisioning route.*/
            SCIMCommonUtils.setThreadLocalIsManagedThroughSCIMEP(true);
            userNames = carbonUM.getUserList(SCIMConstants.ID_URI, userId, UserCoreConstants.DEFAULT_PROFILE);
            String userStoreDomainFromSP = null;
            try {
                userStoreDomainFromSP = getUserStoreDomainFromSP();
            } catch (IdentityApplicationManagementException e) {
                throw new CharonException("Error retrieving User Store name. ", e);
            }
            if (userNames == null || userNames.length == 0) {
                //resource with given id not found
                if (log.isDebugEnabled()) {
                    log.debug("User with id: " + userId + " not found.");
                }
                throw new NotFoundException();
            } else if (userStoreDomainFromSP != null && !(userStoreDomainFromSP
                    .equalsIgnoreCase(UserCoreUtil.extractDomainFromName(userNames[0])))) {
                throw new CharonException("User :" + userNames[0] + "is not belong to user store "
                        + userStoreDomainFromSP + "Hence user updating fail");
            } else {
                //we assume (since id is unique per user) only one user exists for a given id
                userName = userNames[0];
                carbonUM.deleteUser(userName);
                log.info("User: " + userName + " is deleted through SCIM.");
            }

        } catch (org.wso2.carbon.user.core.UserStoreException e) {
            throw new CharonException("Error in deleting user: " + userName, e);
        }
    }

    @Override
    public Group createGroup(Group group) throws CharonException, DuplicateResourceException {

        if (log.isDebugEnabled()) {
            log.debug("Creating group: " + group.getDisplayName());
        }
        try {
            //modify display name if no domain is specified, in order to support multiple user store feature
            String originalName = group.getDisplayName();
            String roleNameWithDomain = null;
            String domainName = "";
            try {
                if (getUserStoreDomainFromSP() != null) {
                    domainName = getUserStoreDomainFromSP();
                    roleNameWithDomain = UserCoreUtil
                            .addDomainToName(UserCoreUtil.removeDomainFromName(originalName), domainName);
                } else if (originalName.indexOf(CarbonConstants.DOMAIN_SEPARATOR) > 0) {
                    roleNameWithDomain = originalName;
                    domainName = originalName.split(UserCoreConstants.DOMAIN_SEPARATOR)[0];
                } else {
                    roleNameWithDomain = UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME
                            + CarbonConstants.DOMAIN_SEPARATOR + originalName;
                    domainName = UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME;
                }
            } catch (IdentityApplicationManagementException e) {
                throw new CharonException("Error retrieving User Store name. ", e);
            }
            group.setDisplayName(roleNameWithDomain);
            //check if the group already exists
            if (carbonUM.isExistingRole(group.getDisplayName(), false)) {
                String error = "Group with name: " + group.getDisplayName() + " already exists in the system.";
                throw new DuplicateResourceException(error);
            }

            /*set thread local property to signal the downstream SCIMUserOperationListener
            about the provisioning route.*/
            SCIMCommonUtils.setThreadLocalIsManagedThroughSCIMEP(true);
            /*if members are sent when creating the group, check whether users already exist in the
            user store*/
            List<String> userIds = group.getMembers();
            List<String> userDisplayNames = group.getMembersWithDisplayName();
            if (CollectionUtils.isNotEmpty(userIds)) {
                List<String> members = new ArrayList<>();
                for (String userId : userIds) {
                    String[] userNames = carbonUM.getUserList(SCIMConstants.ID_URI, userId,
                            UserCoreConstants.DEFAULT_PROFILE);
                    if (userNames == null || userNames.length == 0) {
                        String error = "User: " + userId + " doesn't exist in the user store. "
                                + "Hence, can not create the group: " + group.getDisplayName();
                        throw new IdentitySCIMException(error);
                    } else if (userNames[0].indexOf(UserCoreConstants.DOMAIN_SEPARATOR) > 0
                            && !StringUtils.containsIgnoreCase(userNames[0], domainName)) {
                        String error = "User: " + userId + " doesn't exist in the same user store. "
                                + "Hence, can not create the group: " + group.getDisplayName();
                        throw new IdentitySCIMException(error);
                    } else {
                        members.add(userNames[0]);
                        if (CollectionUtils.isNotEmpty(userDisplayNames)) {
                            boolean userContains = false;
                            for (String user : userDisplayNames) {
                                user = user.indexOf(UserCoreConstants.DOMAIN_SEPARATOR) > 0
                                        ? user.split(UserCoreConstants.DOMAIN_SEPARATOR)[1]
                                        : user;
                                if (user.equalsIgnoreCase(
                                        userNames[0].indexOf(UserCoreConstants.DOMAIN_SEPARATOR) > 0
                                                ? userNames[0].split(UserCoreConstants.DOMAIN_SEPARATOR)[1]
                                                : userNames[0])) {
                                    userContains = true;
                                    break;
                                }
                            }
                            if (!userContains) {
                                throw new IdentitySCIMException("Given SCIM user Id and name not matching..");
                            }
                        }
                    }
                }

                //add other scim attributes in the identity DB since user store doesn't support some attributes.
                SCIMGroupHandler scimGroupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
                scimGroupHandler.createSCIMAttributes(group);
                carbonUM.addRole(group.getDisplayName(), members.toArray(new String[members.size()]), null, false);
                log.info("Group: " + group.getDisplayName() + " is created through SCIM.");
            } else {
                //add other scim attributes in the identity DB since user store doesn't support some attributes.
                SCIMGroupHandler scimGroupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
                scimGroupHandler.createSCIMAttributes(group);
                carbonUM.addRole(group.getDisplayName(), null, null, false);
                log.info("Group: " + group.getDisplayName() + " is created through SCIM.");
            }
        } catch (UserStoreException e) {
            try {
                SCIMGroupHandler scimGroupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
                scimGroupHandler.deleteGroupAttributes(group.getDisplayName());
            } catch (UserStoreException | IdentitySCIMException ex) {
                log.error("Error occurred while doing rollback operation of the SCIM table entry for role: "
                        + group.getDisplayName(), ex);
                throw new CharonException(
                        "Error occurred while doing rollback operation of the SCIM table entry for role: "
                                + group.getDisplayName(),
                        e);
            }
            throw new CharonException("Error occurred while adding role : " + group.getDisplayName(), e);
        } catch (IdentitySCIMException e) {
            //This exception can occurr because of scimGroupHandler.createSCIMAttributes(group) or
            //userContains=false. Therefore contextual message could not be provided.
            throw new CharonException("Error in creating group", e);
        }
        //TODO:after the group is added, read it from user store and return
        return group;
    }

    @Override
    public Group getGroup(String id) throws CharonException {
        if (log.isDebugEnabled()) {
            log.debug("Retrieving group with id: " + id);
        }
        Group group = null;
        try {
            SCIMGroupHandler groupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
            //get group name by Id
            String groupName = groupHandler.getGroupName(id);

            if (groupName != null) {
                group = getGroupWithName(groupName);
            } else {
                //returning null will send a resource not found error to client by Charon.
                return null;
            }
        } catch (org.wso2.carbon.user.core.UserStoreException e) {
            throw new CharonException("Error in retrieving group : " + id, e);
        } catch (IdentitySCIMException e) {
            throw new CharonException("Error in retrieving SCIM Group information from database.", e);
        }
        return group;
    }

    @Override
    public List<Group> listGroups() throws CharonException {
        List<Group> groupList = new ArrayList<>();
        try {
            SCIMGroupHandler groupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
            Set<String> roleNames = groupHandler.listSCIMRoles();
            for (String roleName : roleNames) {
                Group group = this.getGroupOnlyWithMetaAttributes(roleName);
                if (group.getId() != null) {
                    groupList.add(group);
                }
            }
        } catch (org.wso2.carbon.user.core.UserStoreException e) {
            String errMsg = "Error in obtaining role names from user store.";
            errMsg += e.getMessage();
            throw new CharonException(errMsg, e);
        } catch (IdentitySCIMException e) {
            throw new CharonException("Error in retrieving SCIM Group information from database.", e);
        }
        return groupList;
    }

    @Override
    public List<Group> listGroupsByAttribute(Attribute attribute) throws CharonException {
        return Collections.emptyList();
    }

    @Override
    public List<Group> listGroupsByFilter(String filterAttribute, String filterOperation, String attributeValue)
            throws CharonException {
        //since we only support "eq" filter operation for group name currently, no need to check for that.
        if (log.isDebugEnabled()) {
            log.debug("Listing groups with filter: " + filterAttribute + filterOperation + attributeValue);
        }
        List<Group> filteredGroups = new ArrayList<>();
        Group group = null;
        try {
            if (attributeValue != null && carbonUM.isExistingRole(attributeValue, false)) {
                //skip internal roles
                if ((CarbonConstants.REGISTRY_ANONNYMOUS_ROLE_NAME.equals(attributeValue))
                        || UserCoreUtil.isEveryoneRole(attributeValue, carbonUM.getRealmConfiguration())
                        || UserCoreUtil.isPrimaryAdminRole(attributeValue, carbonUM.getRealmConfiguration())) {
                    throw new IdentitySCIMException("Internal roles do not support SCIM.");
                }
                /********we expect only one result**********/
                //construct the group name with domain -if not already provided, in order to support
                //multiple user store feature with SCIM.
                String groupNameWithDomain = null;
                if (attributeValue.indexOf(CarbonConstants.DOMAIN_SEPARATOR) > 0) {
                    groupNameWithDomain = attributeValue;
                } else {
                    groupNameWithDomain = UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME
                            + CarbonConstants.DOMAIN_SEPARATOR + attributeValue;
                }
                group = getGroupOnlyWithMetaAttributes(groupNameWithDomain);
                filteredGroups.add(group);
            } else {
                //returning null will send a resource not found error to client by Charon.
                return Collections.emptyList();
            }
        } catch (org.wso2.carbon.user.core.UserStoreException e) {
            throw new CharonException("Error in filtering groups by attribute name : " + filterAttribute + ", "
                    + "attribute value : " + attributeValue + " and filter operation " + filterOperation, e);
        } catch (org.wso2.carbon.user.api.UserStoreException e) {
            throw new CharonException(
                    "Error in filtering group with filter: " + filterAttribute + filterOperation + attributeValue,
                    e);
        } catch (IdentitySCIMException e) {
            throw new CharonException("Error in retrieving SCIM Group information from database.", e);
        }
        return filteredGroups;
    }

    @Override
    public List<Group> listGroupsBySort(String s, String s1) throws CharonException {
        return Collections.emptyList();
    }

    @Override
    public List<Group> listGroupsWithPagination(int i, int i1) {
        return Collections.emptyList();
    }

    @Override
    public Group updateGroup(Group oldGroup, Group newGroup) throws CharonException {

        try {
            String userStoreDomainFromSP = getUserStoreDomainFromSP();
            if (userStoreDomainFromSP != null && !userStoreDomainFromSP
                    .equalsIgnoreCase(UserCoreUtil.extractDomainFromName(oldGroup.getDisplayName()))) {
                throw new CharonException("Group :" + oldGroup.getDisplayName() + "is not belong to user store "
                        + userStoreDomainFromSP + "Hence group updating fail");
            }
        } catch (IdentityApplicationManagementException e) {
            throw new CharonException("Error retrieving User Store name. ", e);
        }
        oldGroup.setDisplayName(
                UserCoreUtil.addDomainToName(UserCoreUtil.removeDomainFromName(oldGroup.getDisplayName()),
                        UserCoreUtil.extractDomainFromName(oldGroup.getDisplayName())));
        if (UserCoreUtil.extractDomainFromName(newGroup.getDisplayName())
                .equals(UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME)
                && !(UserCoreUtil.extractDomainFromName(oldGroup.getDisplayName())
                        .equals(UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME))) {
            String userStoreDomain = UserCoreUtil.extractDomainFromName(oldGroup.getDisplayName());
            newGroup.setDisplayName(UserCoreUtil.addDomainToName(newGroup.getDisplayName(), userStoreDomain));
            if (newGroup.getMembers() != null && !(newGroup.getMembers().isEmpty())) {
                newGroup = addDomainToUserMembers(newGroup, userStoreDomain);
            }
        }
        newGroup.setDisplayName(SCIMCommonUtils.getGroupNameWithDomain(newGroup.getDisplayName()));
        oldGroup.setDisplayName(SCIMCommonUtils.getGroupNameWithDomain(oldGroup.getDisplayName()));

        if (log.isDebugEnabled()) {
            log.debug("Updating group: " + oldGroup.getDisplayName());
        }

        /*we need to set the domain name for the newGroup if it doesn't have it */
        // we should be able get the domain name like bellow, cause we set it by force at create group

        try {
            /*set thread local property to signal the downstream SCIMUserOperationListener
            about the provisioning route.*/
            SCIMCommonUtils.setThreadLocalIsManagedThroughSCIMEP(true);

            boolean updated = false;
            /*set thread local property to signal the downstream SCIMUserOperationListener
            about the provisioning route.*/
            SCIMCommonUtils.setThreadLocalIsManagedThroughSCIMEP(true);
            //check if the user ids sent in updated group exist in the user store and the associated user name
            //also a matching one.
            List<String> userIds = newGroup.getMembers();
            List<String> userDisplayNames = newGroup.getMembersWithDisplayName();

            String groupName = newGroup.getDisplayName();
            String userStoreDomainForGroup = UserCoreUtil.extractDomainFromName(groupName);
            /* compare user store domain of group and user store domain of user name , if there is a mismatch do not
             update the group */
            if (userDisplayNames != null && userDisplayNames.size() > 0) {
                for (String userDisplayName : userDisplayNames) {
                    String userStoreDomainForUser = UserCoreUtil.extractDomainFromName(userDisplayName);
                    if (!(UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME.equals(userStoreDomainForGroup))
                            && (UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME.equals(userStoreDomainForUser))) {
                        throw new IdentitySCIMException(
                                "User store domain is not indicated for user :" + userDisplayName);
                    }
                    if (!userStoreDomainForGroup.equalsIgnoreCase(userStoreDomainForUser)) {
                        throw new IdentitySCIMException(userDisplayName + " does not " + "belongs to user store "
                                + userStoreDomainForGroup);
                    }

                }
            }

            if (CollectionUtils.isNotEmpty(userIds)) {
                String[] userNames = null;
                for (String userId : userIds) {
                    userNames = carbonUM.getUserList(SCIMConstants.ID_URI,
                            UserCoreUtil.addDomainToName(userId, userStoreDomainForGroup),
                            UserCoreConstants.DEFAULT_PROFILE);
                    if (userNames == null || userNames.length == 0) {
                        String error = "User: " + userId + " doesn't exist in the user store. "
                                + "Hence, can not update the group: " + oldGroup.getDisplayName();
                        throw new IdentitySCIMException(error);
                    } else {
                        if (!UserCoreUtil.isContain(UserCoreUtil.removeDomainFromName(userNames[0]),
                                UserCoreUtil.removeDomainFromNames(
                                        userDisplayNames.toArray(new String[userDisplayNames.size()])))) {
                            throw new IdentitySCIMException("Given SCIM user Id and name not matching..");
                        }
                    }
                }
            }
            //we do not update Identity_SCIM DB here since it is updated in SCIMUserOperationListener's methods.

            //update name if it is changed
            if (!(oldGroup.getDisplayName().equalsIgnoreCase(newGroup.getDisplayName()))) {
                //update group name in carbon UM
                carbonUM.updateRoleName(oldGroup.getDisplayName().toLowerCase(),
                        newGroup.getDisplayName().toLowerCase());

                updated = true;
            }

            //find out added members and deleted members..
            List<String> oldMembers = oldGroup.getMembersWithDisplayName();
            List<String> newMembers = newGroup.getMembersWithDisplayName();
            if (newMembers != null) {

                List<String> addedMembers = new ArrayList<>();
                List<String> deletedMembers = new ArrayList<>();

                //check for deleted members
                if (CollectionUtils.isNotEmpty(oldMembers)) {
                    for (String oldMember : oldMembers) {
                        if (newMembers != null && newMembers.contains(oldMember)) {
                            continue;
                        }
                        deletedMembers.add(oldMember);
                    }
                }

                //check for added members
                if (CollectionUtils.isNotEmpty(newMembers)) {
                    for (String newMember : newMembers) {
                        if (oldMembers != null && oldMembers.contains(newMember)) {
                            continue;
                        }
                        addedMembers.add(newMember);
                    }
                }

                if (CollectionUtils.isNotEmpty(addedMembers) || CollectionUtils.isNotEmpty(deletedMembers)) {
                    carbonUM.updateUserListOfRole(newGroup.getDisplayName(),
                            deletedMembers.toArray(new String[deletedMembers.size()]),
                            addedMembers.toArray(new String[addedMembers.size()]));
                    updated = true;
                }
            }
            if (updated) {
                log.info("Group: " + newGroup.getDisplayName() + " is updated through SCIM.");
            } else {
                log.warn("There is no updated field in the group: " + oldGroup.getDisplayName()
                        + ". Therefore ignoring the provisioning.");
            }

        } catch (UserStoreException | IdentitySCIMException e) {
            throw new CharonException("Error occurred while updating old group : " + oldGroup.getDisplayName(), e);
        }
        return newGroup;
    }

    @Override
    public Group updateGroup(List<Attribute> attributes) throws CharonException {
        return null;
    }

    /**
     * this method similar to updateGroup but new changes to group will be merged without changing existing data
     *
     * @param oldGroup existing group meta information
     * @param newGroup new changes required for existing group
     * @return updated group information
     * @throws CharonException
     */
    @Override
    public Group patchGroup(Group oldGroup, Group newGroup) throws CharonException {
        try {
            String userStoreDomainFromSP = getUserStoreDomainFromSP();
            if (userStoreDomainFromSP != null && !userStoreDomainFromSP
                    .equalsIgnoreCase(UserCoreUtil.extractDomainFromName(oldGroup.getDisplayName()))) {
                throw new CharonException("Group :" + oldGroup.getDisplayName() + "is not belong to user store "
                        + userStoreDomainFromSP + "Hence group updating fail");
            }
        } catch (IdentityApplicationManagementException e) {
            throw new CharonException("Error retrieving User Store name. ", e);
        }
        oldGroup.setDisplayName(
                UserCoreUtil.addDomainToName(UserCoreUtil.removeDomainFromName(oldGroup.getDisplayName()),
                        UserCoreUtil.extractDomainFromName(oldGroup.getDisplayName())));

        if (log.isDebugEnabled()) {
            log.debug("Updating group: " + oldGroup.getDisplayName());//add from group new name
        }

        /*
         * we need to set the domain name for the newGroup if it doesn't
         * have it
         */
        // we should be able get the domain name like bellow, cause we set
        // it by force at create group

        try {
            /*
             * set thread local property to signal the downstream
             * SCIMUserOperationListener
             * about the provisioning route.
             */
            SCIMCommonUtils.setThreadLocalIsManagedThroughSCIMEP(true);

            boolean updated = false;
            /*
             * set thread local property to signal the downstream
             * SCIMUserOperationListener
             * about the provisioning route.
             */
            SCIMCommonUtils.setThreadLocalIsManagedThroughSCIMEP(true);
            // check if the user ids sent in updated group exist in the user
            // store and the associated user name
            // also a matching one.

            List<String> userIds = newGroup.getMembers();
            List<String> userDisplayNames = newGroup.getMembersWithDisplayName();
            String[] userNames = null;
            if (userIds != null) {
                for (String userId : userIds) {
                    userNames = carbonUM.getUserList(SCIMConstants.ID_URI, userId,
                            UserCoreConstants.DEFAULT_PROFILE);
                    if (userNames == null || userNames.length == 0) {
                        String error = "User: " + userId + " doesn't exist in the user store. "
                                + "Hence, can not update the group: " + oldGroup.getDisplayName();
                        throw new CharonException(error);
                    } else {
                        if (!userDisplayNames.contains(userNames[0])) {
                            throw new CharonException("Given SCIM user Id and name not matching..");
                        }
                    }
                }
            }

            if (UserCoreUtil.extractDomainFromName(newGroup.getDisplayName())
                    .equals(UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME)
                    && !(UserCoreUtil.extractDomainFromName(oldGroup.getDisplayName())
                            .equals(UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME))) {
                String userStoreDomain = UserCoreUtil.extractDomainFromName(oldGroup.getDisplayName());
                newGroup.setDisplayName(UserCoreUtil.addDomainToName(newGroup.getDisplayName(), userStoreDomain));
                if (newGroup.getMembers() != null && !newGroup.getMembers().isEmpty()) {
                    newGroup = addDomainToUserMembers(newGroup, userStoreDomain);
                }
            }

            // we do not update Identity_SCIM DB here since it is updated in
            // SCIMUserOperationListener's methods.

            // update name if it is changed
            if (!(oldGroup.getDisplayName().equalsIgnoreCase(newGroup.getDisplayName()))) {
                // update group name in carbon UM
                carbonUM.updateRoleName(oldGroup.getDisplayName().toLowerCase(),
                        newGroup.getDisplayName().toLowerCase());

                updated = true;
            }

            //SCIM request does not have operation attribute for new members need be added hence parsing null
            List<String> addRequestedMembers = newGroup.getMembersWithDisplayName(null);
            List<String> deleteRequestedMembers = newGroup
                    .getMembersWithDisplayName(SCIMConstants.CommonSchemaConstants.OPERATION_DELETE);

            //Handling meta data attributes coming from SCIM request. Through meta attributes all existing members
            // can be replaced with new set of members
            if (newGroup.getAttributesOfMeta() != null
                    && SCIMConstants.GroupSchemaConstants.MEMBERS.equals(newGroup.getAttributesOfMeta().get(0))) {
                if (!deleteRequestedMembers.isEmpty()) {
                    log.warn("All Existing members will be deleted through SCIM meta attributes Hence operation "
                            + "delete is Invalid");
                    deleteRequestedMembers = new ArrayList<>();
                }
                String users[] = carbonUM.getUserListOfRole(newGroup.getDisplayName());
                if (addRequestedMembers.isEmpty()) {
                    carbonUM.updateUserListOfRole(newGroup.getDisplayName(), users, new String[0]);
                } else {
                    //If new set of members contains an old members, save those old members without deleting from user store
                    List<String> membersDeleteFromUserStore = new ArrayList<String>();
                    for (String user : users) {
                        if (!addRequestedMembers.contains(user)) {
                            membersDeleteFromUserStore.add(user);
                        }
                    }
                    carbonUM.updateUserListOfRole(newGroup.getDisplayName(),
                            membersDeleteFromUserStore.toArray(new String[membersDeleteFromUserStore.size()]),
                            new String[0]);
                }
            }
            // find out added members and deleted members..
            List<String> oldMembers = oldGroup.getMembersWithDisplayName();

            List<String> addedMembers = new ArrayList<>();
            List<String> deletedMembers = new ArrayList<>();

            if (addRequestedMembers.isEmpty() && deleteRequestedMembers.isEmpty()) {
                String users[] = carbonUM.getUserListOfRole(newGroup.getDisplayName());
                deletedMembers = Arrays.asList(users);
            }

            for (String addRequestedMember : addRequestedMembers) {
                if ((!oldMembers.isEmpty()) && oldMembers.contains(addRequestedMember)) {
                    continue;
                }
                addedMembers.add(addRequestedMember);
            }

            for (String deleteRequestedMember : deleteRequestedMembers) {
                if ((!oldMembers.isEmpty()) && oldMembers.contains(deleteRequestedMember)) {
                    deletedMembers.add(deleteRequestedMember);
                } else {
                    continue;
                }
            }

            if (newGroup.getDisplayName() != null && ((CollectionUtils.isNotEmpty(addedMembers))
                    || (CollectionUtils.isNotEmpty(deletedMembers)))) {
                carbonUM.updateUserListOfRole(newGroup.getDisplayName(),
                        deletedMembers.toArray(new String[deletedMembers.size()]),
                        addedMembers.toArray(new String[addedMembers.size()]));
                updated = true;
            }

            if (updated) {
                log.info("Group: " + newGroup.getDisplayName() + " is updated through SCIM.");
            } else {
                log.warn("There is no updated field in the group: " + oldGroup.getDisplayName()
                        + ". Therefore ignoring the provisioning.");
            }

        } catch (UserStoreException e) {
            //throwing real message coming from carbon user manager
            throw new CharonException("Error in patching group", e);
        }
        return newGroup;
    }

    @Override
    public void deleteGroup(String groupId) throws NotFoundException, CharonException {

        if (log.isDebugEnabled()) {
            log.debug("Deleting group: " + groupId);
        }
        try {
            /*set thread local property to signal the downstream SCIMUserOperationListener
            about the provisioning route.*/
            SCIMCommonUtils.setThreadLocalIsManagedThroughSCIMEP(true);

            //get group name by id
            SCIMGroupHandler groupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
            String groupName = groupHandler.getGroupName(groupId);

            if (groupName != null) {
                String userStoreDomainFromSP = null;
                try {
                    userStoreDomainFromSP = getUserStoreDomainFromSP();
                } catch (IdentityApplicationManagementException e) {
                    throw new CharonException("Error retrieving User Store name. ", e);
                }
                if (userStoreDomainFromSP != null && !(userStoreDomainFromSP
                        .equalsIgnoreCase(UserCoreUtil.extractDomainFromName(groupName)))) {
                    throw new CharonException("Group :" + groupName + "is not belong to user store "
                            + userStoreDomainFromSP + "Hence group updating fail");
                }
                //delete group in carbon UM
                carbonUM.deleteRole(groupName);

                //we do not update Identity_SCIM DB here since it is updated in SCIMUserOperationListener's methods.
                log.info("Group: " + groupName + " is deleted through SCIM.");

            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Group with SCIM id: " + groupId + " doesn't exist in the system.");
                }
                throw new NotFoundException();
            }
        } catch (UserStoreException | IdentitySCIMException e) {
            throw new CharonException("Error occurred while deleting group " + groupId, e);
        }
    }

    private User getSCIMMetaUser(String userName) throws CharonException {

        List<String> claimURIList = new ArrayList<>();
        claimURIList.add(SCIMConstants.ID_URI);
        claimURIList.add(SCIMConstants.META_LOCATION_URI);
        claimURIList.add(SCIMConstants.META_CREATED_URI);
        claimURIList.add(SCIMConstants.META_LAST_MODIFIED_URI);
        User scimUser = null;

        try {
            Map<String, String> attributes = carbonUM.getUserClaimValues(userName,
                    claimURIList.toArray(new String[claimURIList.size()]), null);
            attributes.put(SCIMConstants.USER_NAME_URI, userName);
            scimUser = (User) AttributeMapper.constructSCIMObjectFromAttributes(attributes, SCIMConstants.USER_INT);
        } catch (UserStoreException | CharonException | NotFoundException e) {
            throw new CharonException(
                    "Error in getting user information from Carbon User Store for " + "user: " + userName + " ", e);
        }
        return scimUser;
    }

    private User getSCIMUser(String userName, List<String> claimURIList) throws CharonException {
        User scimUser = null;
        try {

            //obtain user claim values
            Map<String, String> attributes = carbonUM.getUserClaimValues(userName,
                    claimURIList.toArray(new String[claimURIList.size()]), null);
            //skip simple type addresses claim coz it is complex with sub types in the schema
            if (attributes.containsKey(SCIMConstants.ADDRESSES_URI)) {
                attributes.remove(SCIMConstants.ADDRESSES_URI);
            }

            // Add username with domain name
            attributes.put(SCIMConstants.USER_NAME_URI, userName);

            //get groups of user and add it as groups attribute
            String[] roles = carbonUM.getRoleListOfUser(userName);
            //construct the SCIM Object from the attributes
            scimUser = (User) AttributeMapper.constructSCIMObjectFromAttributes(attributes, SCIMConstants.USER_INT);
            //add groups of user:
            for (String role : roles) {
                if (UserCoreUtil.isEveryoneRole(role, carbonUM.getRealmConfiguration())
                        || UserCoreUtil.isPrimaryAdminRole(role, carbonUM.getRealmConfiguration())
                        || CarbonConstants.REGISTRY_ANONNYMOUS_ROLE_NAME.equalsIgnoreCase(role)
                        || role.toLowerCase()
                                .startsWith((UserCoreConstants.INTERNAL_DOMAIN + CarbonConstants.DOMAIN_SEPARATOR)
                                        .toLowerCase())) {
                    // carbon specific roles do not possess SCIM info, hence
                    // skipping them.
                    // skip intenal roles
                    continue;
                }
                Group group = getGroupOnlyWithMetaAttributes(role);
                if (group != null) { // can be null for non SCIM groups
                    scimUser.setGroup(null, group.getId(), role);
                }
            }
        } catch (UserStoreException | CharonException | NotFoundException | IdentitySCIMException e) {
            throw new CharonException("Error in getting user information for user: " + userName, e);
        }
        return scimUser;
    }

    /**
     * Get the full group with all the details including users.
     *
     * @param groupName
     * @return
     * @throws CharonException
     * @throws org.wso2.carbon.user.core.UserStoreException
     * @throws IdentitySCIMException
     */
    private Group getGroupWithName(String groupName)
            throws CharonException, org.wso2.carbon.user.core.UserStoreException, IdentitySCIMException {

        Group group = new Group();
        group.setDisplayName(groupName);
        String[] userNames = carbonUM.getUserListOfRole(groupName);

        //get the ids of the users and set them in the group with id + display name
        if (userNames != null && userNames.length != 0) {
            for (String userName : userNames) {
                String userId = carbonUM.getUserClaimValue(userName, SCIMConstants.ID_URI, null);
                group.setMember(userId, userName);
            }
        }
        //get other group attributes and set.
        SCIMGroupHandler groupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
        group = groupHandler.getGroupWithAttributes(group, groupName);
        return group;
    }

    /**
     * Get group with only meta attributes.
     *
     * @param groupName
     * @return
     * @throws CharonException
     * @throws IdentitySCIMException
     * @throws org.wso2.carbon.user.core.UserStoreException
     */
    private Group getGroupOnlyWithMetaAttributes(String groupName)
            throws CharonException, IdentitySCIMException, org.wso2.carbon.user.core.UserStoreException {
        //get other group attributes and set.
        Group group = new Group();
        group.setDisplayName(groupName);
        SCIMGroupHandler groupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
        return groupHandler.getGroupWithAttributes(group, groupName);
    }

    private void provision(ProvisioningOperation provisioningMethod, SCIMObject provisioningObject)
            throws CharonException {

        String domainName = UserCoreUtil.getDomainName(carbonUM.getRealmConfiguration());
        try {

            Map<org.wso2.carbon.identity.application.common.model.ClaimMapping, List<String>> outboundAttributes = new HashMap<org.wso2.carbon.identity.application.common.model.ClaimMapping, List<String>>();

            if (provisioningObject.getAttribute("password") != null) {
                outboundAttributes.put(
                        org.wso2.carbon.identity.application.common.model.ClaimMapping
                                .build(IdentityProvisioningConstants.PASSWORD_CLAIM_URI, null, null, false),
                        Arrays.asList(new String[] { ((SimpleAttribute) provisioningObject.getAttribute("password"))
                                .getStringValue() }));
            }

            if (provisioningObject.getAttribute(USER_NAME_STRING) != null) {
                outboundAttributes.put(
                        org.wso2.carbon.identity.application.common.model.ClaimMapping
                                .build(IdentityProvisioningConstants.USERNAME_CLAIM_URI, null, null, false),
                        Arrays.asList(
                                new String[] { ((SimpleAttribute) provisioningObject.getAttribute(USER_NAME_STRING))
                                        .getStringValue() }));
            }

            String domainAwareName = UserCoreUtil
                    .addDomainToName(provisioningObject.getAttribute(USER_NAME_STRING).getName(), domainName);
            ProvisioningEntity provisioningEntity = new ProvisioningEntity(ProvisioningEntityType.USER,
                    domainAwareName, provisioningMethod, outboundAttributes);
            Map<String, String> inboundAttributes = AttributeMapper
                    .getClaimsMap((AbstractSCIMObject) provisioningObject);

            provisioningEntity.setInboundAttributes(inboundAttributes);
            String tenantDomainName = CarbonContext.getThreadLocalCarbonContext().getTenantDomain();

            ThreadLocalProvisioningServiceProvider threadLocalServiceProvider;
            threadLocalServiceProvider = IdentityApplicationManagementUtil
                    .getThreadLocalProvisioningServiceProvider();

            if (threadLocalServiceProvider != null) {

                String serviceProvider = threadLocalServiceProvider.getServiceProviderName();
                tenantDomainName = threadLocalServiceProvider.getTenantDomain();
                if (threadLocalServiceProvider.getServiceProviderType() == ProvisioningServiceProviderType.OAUTH) {
                    try {
                        serviceProvider = ApplicationManagementService.getInstance()
                                .getServiceProviderNameByClientId(
                                        threadLocalServiceProvider.getServiceProviderName(), "oauth2",
                                        tenantDomainName);
                    } catch (IdentityApplicationManagementException e) {
                        log.error("Error while provisioning", e);
                        return;
                    }
                }

                // call framework method to provision the user.
                OutboundProvisioningManager.getInstance().provision(provisioningEntity, serviceProvider,
                        threadLocalServiceProvider.getClaimDialect(), tenantDomainName,
                        threadLocalServiceProvider.isJustInTimeProvisioning());
            } else {
                // call framework method to provision the user.
                OutboundProvisioningManager.getInstance().provision(provisioningEntity,
                        ApplicationConstants.LOCAL_SP, DefaultInboundUserProvisioningListener.WSO2_CARBON_DIALECT,
                        tenantDomainName, false);
            }

        } catch (NotFoundException e) {
            throw new CharonException("Failed to find resource in external user store.", e);
        } catch (IdentityProvisioningException e) {
            throw new CharonException("Error while provisioning to externaluser store in dumb mode.", e);
        }
    }

    private String getUserStoreDomainFromSP() throws IdentityApplicationManagementException {

        ThreadLocalProvisioningServiceProvider threadLocalSP = IdentityApplicationManagementUtil
                .getThreadLocalProvisioningServiceProvider();
        ServiceProvider serviceProvider = null;
        if (threadLocalSP.getServiceProviderType() == ProvisioningServiceProviderType.OAUTH) {
            serviceProvider = ApplicationManagementService.getInstance().getServiceProviderByClientId(
                    threadLocalSP.getServiceProviderName(), "oauth2", threadLocalSP.getTenantDomain());
        } else {
            serviceProvider = ApplicationManagementService.getInstance()
                    .getServiceProvider(threadLocalSP.getServiceProviderName(), threadLocalSP.getTenantDomain());
        }

        if (serviceProvider != null && serviceProvider.getInboundProvisioningConfig() != null && !StringUtils
                .isBlank(serviceProvider.getInboundProvisioningConfig().getProvisioningUserStore())) {
            return serviceProvider.getInboundProvisioningConfig().getProvisioningUserStore();
        }
        return null;
    }

    private Group addDomainToUserMembers(Group group, String userStoreDomain) throws CharonException {
        List<String> membersId = group.getMembers();

        if (StringUtils.isBlank(userStoreDomain) || membersId == null || membersId.isEmpty()) {
            return group;
        }

        Map<String, String> userMembers = mergeSCIMIDsWithDisplayNames(group);

        //remove all existing user members to add user members with user store domain
        for (String memberId : membersId) {
            group.removeMember(memberId);
        }

        //add user members with user store domain
        for (Map.Entry<String, String> entry : userMembers.entrySet()) {
            group.setMember(entry.getKey(), UserCoreUtil.addDomainToName(entry.getValue(), userStoreDomain));
        }

        return group;
    }

    /**
     * In current charon implementation there is no way to associate SCIM ID with display name for user member, hence
     * adding association to map
     *
     * @param group
     * @return
     * @throws CharonException
     */
    private Map<String, String> mergeSCIMIDsWithDisplayNames(Group group) throws CharonException {
        List<String> membersId = group.getMembers();
        List<String> membersDisplayNames = group.getMembersWithDisplayName();
        Map<String, String> userMembers = new HashMap<>();
        for (int i = 0; i < membersId.size(); i++) {
            userMembers.put(membersId.get(i), membersDisplayNames.get(i));
        }
        return userMembers;
    }
}