class TargetHelper(object):
    """
    Shared code for targeting clusters and servers. Domain create and update use this code.
    """
    __class_name = 'TargetHelper'

    def __init__(self, model, model_context, aliases, exception_type, logger):
        self.logger = logger
        self.model = model
        self.model_context = model_context
        self.aliases = aliases
        self.wlst_helper = WlstHelper(exception_type)
        self.wls_helper = WebLogicHelper(self.logger)
        self.exception_type = exception_type
        self.domain_typedef = self.model_context.get_domain_typedef()
        topology = model.get_model_topology()
        if ADMIN_SERVER_NAME in topology:
            self._admin_server_name = topology[ADMIN_SERVER_NAME]
        else:
            self._admin_server_name = DEFAULT_ADMIN_SERVER_NAME

    def target_jrf_groups_to_clusters_servers(self):
        """
        Call applyJRF to for those versions of wlst that cannot target servers to server groups.
        This assigns the JRF resources to all managed servers. If the managed server is in a
        cluster, this method assigns the JRF resources are assigned to the cluster. Else, if
        the managed server is stand-alone, the resources are assigned to the managed server.
        to automatically update the domain.
        """
        _method_name = 'target_jrf_groups_to_clusters_servers'
        self.logger.entering(class_name=self.__class_name,
                             method_name=_method_name)

        location = LocationContext()
        root_path = self.aliases.get_wlst_attributes_path(location)
        self.wlst_helper.cd(root_path)
        admin_server_name = self.wlst_helper.get(ADMIN_SERVER_NAME)

        # We need to get the effective list of servers for the domain.  Since any servers
        # referenced in the model have already been created but the templates may have
        # defined new servers not listed in the model, get the list from WLST.
        server_names = self.get_existing_server_names()
        if admin_server_name in server_names:
            server_names.remove(admin_server_name)

        # Get the clusters and and their members
        cluster_map = self._get_clusters_and_members_map()

        # Get the clusters and and their members
        for cluster_name, cluster_servers in cluster_map.iteritems():
            self.logger.info('WLSDPLY-12233',
                             'Cluster',
                             cluster_name,
                             class_name=self.__class_name,
                             method_name=_method_name)
            self.wlst_helper.apply_jrf(cluster_name, self.model_context)
            for member in cluster_servers:
                if member in server_names:
                    server_names.remove(member)
        for ms_name in server_names:
            self.logger.info('WLSDPLY-12233',
                             'Managed Server',
                             ms_name,
                             class_name=self.__class_name,
                             method_name=_method_name)
            self.wlst_helper.apply_jrf(ms_name, self.model_context)

        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name)
        return

    def target_server_groups_to_servers(self, server_groups_to_target):
        """
        Target the server groups to the servers.
        :param server_groups_to_target: the list of server groups to target
        :raises: BundleAwareException of the specified type: if an error occurs
        """
        _method_name = 'target_server_groups_to_servers'

        self.logger.entering(server_groups_to_target,
                             class_name=self.__class_name,
                             method_name=_method_name)
        if len(server_groups_to_target) == 0:
            return list()

        location = LocationContext()
        root_path = self.aliases.get_wlst_attributes_path(location)
        self.wlst_helper.cd(root_path)

        # We need to get the effective list of servers for the domain.  Since any servers
        # referenced in the model have already been created but the templates may have
        # defined new servers not listed in the model, get the list from WLST.
        server_names = self._get_existing_server_names()
        # Get the clusters and and their members
        cluster_map = self._get_clusters_and_members_map()
        # Get any limits that may have been defined in the model
        domain_info = self.model.get_model_domain_info()
        server_group_targeting_limits = \
            dictionary_utils.get_dictionary_element(domain_info, SERVER_GROUP_TARGETING_LIMITS)
        if len(server_group_targeting_limits) > 0:
            server_group_targeting_limits = \
                self._get_server_group_targeting_limits(server_group_targeting_limits, cluster_map)

        self.logger.finer('WLSDPLY-12240',
                          str(server_group_targeting_limits),
                          class_name=self.__class_name,
                          method_name=_method_name)

        # Get the map of server names to server groups to target
        server_to_server_groups_map =\
            self._get_server_to_server_groups_map(self._admin_server_name,
                                                  server_names,
                                                  server_groups_to_target,
                                                  server_group_targeting_limits)  # type: dict
        self.logger.finer('WLSDPLY-12242',
                          str(server_to_server_groups_map),
                          class_name=self.__class_name,
                          method_name=_method_name)

        final_assignment_map = dict()
        # Target servers and dynamic clusters to the server group resources
        if len(server_names) > 0:
            for server, server_groups in server_to_server_groups_map.iteritems(
            ):
                if len(server_groups) > 0:
                    if server in server_names:
                        final_assignment_map[server] = server_groups

        #
        # Domain has not targeted the server groups to managed servers (configured), or the
        # domain has no managed servers (configured) but has user server groups. The resources for the
        # user server groups must be targeted before the write/update domain or the write/update will fail.
        # Thus assign the user server groups to the admin server.
        #
        # Because of the interaction of the working context in the different wlst helpers, the dynamic
        # clusters will be applied to the resources separately and after the write/update domain.
        #
        # (From original blurb)
        #  This is really a best effort attempt.  It works for JRF domains but it is certainly possible
        # that it may cause problems with other custom domain types.  Of course, creating a domain with
        # no managed servers is not a primary use case of this tool so do it and hope for the best...
        #
        # (New comment)
        # As we have added the intricacies of the dynamic clusters, if the targeting is to dynamic
        # clusters only, the set server groups with the admin server will get through the write/update domain
        # and the applyJRF with the dynamic cluster should theoretically unset the AdminServer on the user server
        # groups. It works with JRF type domains.

        if len(server_groups_to_target) > 0:
            if len(final_assignment_map) == 0:
                # This is a quickie to fix the issue where server groups are not targeted because no configured
                #  managed servers exist in the domain
                final_assignment_map[server_names[0]] = server_groups_to_target
            else:
                # If a server group or groups is not targeted in the assignments, log it to stdout
                no_targets = [
                    server_target for server_target in server_groups_to_target
                    if server_target not in [
                        server_target
                        for row in final_assignment_map.itervalues()
                        for server_target in server_groups_to_target
                        if server_target in row
                    ]
                ]
                if len(no_targets) > 0:
                    self.logger.info('WLSDPLY-12248',
                                     no_targets,
                                     class_name=self.__class_name,
                                     method_name=_method_name)

        self.logger.exiting(result=str(final_assignment_map),
                            class_name=self.__class_name,
                            method_name=_method_name)
        return final_assignment_map

    def target_server_groups_to_dynamic_clusters(self,
                                                 server_groups_to_target):
        """
        Target dynamic clusters to dynamic cluster server groups. Dynamic cluster server groups are not user
        expandable. Thus if a dynamic server group is not targeted to a dynamic cluster, don't target to admin
        server. The dynamic cluster server groups are not required to be targeted.
        :param server_groups_to_target: the list of dynamic cluster server groups to target
        :raises: BundleAwareException of the specified type: if an error occurs
        """
        _method_name = 'target_server_groups_to_dynamic_clusters'

        self.logger.entering(server_groups_to_target,
                             class_name=self.__class_name,
                             method_name=_method_name)
        if len(server_groups_to_target) == 0:
            return list()

        location = LocationContext()
        root_path = self.aliases.get_wlst_attributes_path(location)
        self.wlst_helper.cd(root_path)

        # Get the clusters and and their members
        cluster_map = self._get_clusters_and_members_map()
        dynamic_cluster_names = list()
        for cluster_name in cluster_map:
            if DYNAMIC_SERVERS in cluster_map[cluster_name]:
                dynamic_cluster_names.append(cluster_name)

        domain_info = self.model.get_model_domain_info()
        dc_server_group_targeting_limits = \
            dictionary_utils.get_dictionary_element(domain_info, DYNAMIC_CLUSTER_SERVER_GROUP_TARGETING_LIMITS)
        if len(dc_server_group_targeting_limits) > 0:
            dc_server_group_targeting_limits = \
                self._get_dynamic_cluster_server_group_targeting_limits(dc_server_group_targeting_limits, cluster_map)
        dynamic_cluster_assigns = \
            self.get_dc_to_server_groups_map(dynamic_cluster_names, server_groups_to_target,
                                             dc_server_group_targeting_limits)  # type: dict
        self.logger.finer('WLSDPLY-12240',
                          str(dc_server_group_targeting_limits),
                          class_name=self.__class_name,
                          method_name=_method_name)

        self.logger.exiting(result=str(dynamic_cluster_assigns),
                            class_name=self.__class_name,
                            method_name=_method_name)
        return dynamic_cluster_assigns

    def target_server_groups(self, server_assigns):
        """
        Perform the targeting of the server groups to server from the list of assignments made in the
        target helper assignment step. This is separate from creating the list of assignments in order
        to control the state of the domain when the target is done.
        :param server_assigns: map of server to server group
        """
        _method_name = 'target_server_groups'
        self.logger.entering(str(server_assigns),
                             class_name=self.__class_name,
                             method_name=_method_name)

        for server, server_groups in server_assigns.iteritems():
            server_name = self.wlst_helper.get_quoted_name_for_wlst(server)
            self.logger.info('WLSDPLY-12224',
                             str(server_groups),
                             server_name,
                             class_name=self.__class_name,
                             method_name=_method_name)
            self.wlst_helper.set_server_groups(
                server_name, server_groups,
                self.model_context.get_model_config().
                get_set_server_grps_timeout())

        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name)

    def target_dynamic_server_groups(self, dynamic_cluster_assigns):
        """
        Dynamic clusters need special handling to assign the server group resources to the dynamic cluster.
        You cannot assign servergroups to a server template. So must search each templates that contain the server group
        for resources and specifically add the dynamic target to the resource target.
        If JRF or RestrictedJRF skip the check and do the applyJRF function to automatically target to the cluster.
        :param dynamic_cluster_assigns: The assignments from domainInfo targeting limits applied to dynamic lusters
        """
        _method_name = 'target_dynamic_server_groups'
        self.logger.entering(str(dynamic_cluster_assigns),
                             class_name=self.__class_name,
                             method_name=_method_name)

        domain_typedef = self.model_context.get_domain_typedef()

        if len(dynamic_cluster_assigns) > 0:
            # assign server group resources to cluster based on the version of WebLogic server version.
            if self.wls_helper.is_dynamic_cluster_server_groups_supported():
                bug_map = self.save_dyn_size(dynamic_cluster_assigns)
                self.target_server_groups(dynamic_cluster_assigns)
                self.restore_dyn_size(bug_map)
            elif self.wls_helper.is_dynamic_cluster_server_group_supported():
                bug_map = self.save_dyn_size(dynamic_cluster_assigns)
                self.target_dynamic_clusters(dynamic_cluster_assigns)
                self.restore_dyn_size(bug_map)
            else:
                self.logger.warning('WLSDPLY-12238',
                                    domain_typedef.get_domain_type(),
                                    class_name=self.__class_name,
                                    method_name=_method_name)

        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name)
        return

    def target_dynamic_clusters(self, server_assigns):
        """
        Perform the targeting of a server group to the clusters from the list of assignments made in the
        target helper assignment step. This is separate from creating the list of assignments in order
        to control the state of the domain when the target is done. The version of WebLogic Server
        supports targeting a single server group to a dynamic cluster.
        :param server_assigns: map of server to server group
        """
        _method_name = 'target_dynamic_clusters'
        self.logger.entering(str(server_assigns),
                             class_name=self.__class_name,
                             method_name=_method_name)

        for cluster, server_groups in server_assigns.iteritems():
            cluster_name = self.wlst_helper.get_quoted_name_for_wlst(cluster)
            if len(server_groups) > 1:
                ex = exception_helper.create_exception(self.exception_type,
                                                       'WLSDPLY-12256',
                                                       cluster, server_groups)
                self.logger.throwing(ex,
                                     class_name=self.__class_name,
                                     method_name=_method_name)
                raise ex
            elif len(server_groups) > 0:
                self.logger.info('WLSDPLY-12255',
                                 server_groups[0],
                                 cluster_name,
                                 class_name=self.__class_name,
                                 method_name=_method_name)
                self.wlst_helper.set_server_group_dynamic_cluster(
                    cluster_name, server_groups[0])

        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name)

    def _target_jrf_resources(self, dynamic_cluster_assigns):
        # Target the JRF resources directly using the applyJRF method.
        _method_name = '_target_jrf_resources'
        names_only = list()
        for name in dynamic_cluster_assigns:
            names_only.append(name)
        if self.model_context.is_wlst_online() and \
                self.model_context.get_domain_typedef().is_restricted_jrf_domain_type():
            self.logger.warning('WLSDPLY-12244',
                                str(names_only),
                                class_name=self.__class_name,
                                _method_name=_method_name)
        else:
            self.logger.info('WLSDPLY-12236',
                             str(names_only),
                             class_name=self.__class_name,
                             method_name=_method_name)
            self.wlst_helper.apply_jrf_with_context(names_only,
                                                    self.model_context)

    def _get_existing_server_names(self):
        """
        Get the list of server names from WLST.
        :return: the list of server names
        :raises: BundleAwareException of the specified type: is an error occurs reading from the aliases or WLST
        """
        _method_name = '_get_existing_server_names'

        self.logger.entering(class_name=self.__class_name,
                             method_name=_method_name)
        server_location = LocationContext().append_location(SERVER)
        server_list_path = self.aliases.get_wlst_list_path(server_location)
        result = self.wlst_helper.get_existing_object_list(server_list_path)
        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name,
                            result=result)
        return result

    def _get_clusters_and_members_map(self):
        """
        Get a map keyed by cluster name with values that are a list of member server names
        :return: the cluster name to member server names map
        :raises: BundleAwareException of the specified type: is an error occurs reading from the aliases or WLST
        """
        _method_name = '_get_clusters_and_members_map'

        self.logger.entering(class_name=self.__class_name,
                             method_name=_method_name)
        server_location = LocationContext().append_location(SERVER)
        server_list_path = self.aliases.get_wlst_list_path(server_location)
        server_names = self.wlst_helper.get_existing_object_list(
            server_list_path)
        server_token = self.aliases.get_name_token(server_location)
        cluster_map = OrderedDict()
        for server_name in server_names:
            server_location.add_name_token(server_token, server_name)
            server_attributes_path = self.aliases.get_wlst_attributes_path(
                server_location)
            self.wlst_helper.cd(server_attributes_path)

            server_attributes_map = self.wlst_helper.lsa()
            cluster_name = dictionary_utils.get_element(
                server_attributes_map, CLUSTER)
            if string_utils.is_empty(cluster_name):
                # if server is not part of a cluster, continue with the next server
                continue

            if cluster_name not in cluster_map:
                cluster_map[cluster_name] = list()
            cluster_map[cluster_name].append(server_name)

        clusters_location = LocationContext().append_location(CLUSTER)
        cluster_list_path = self.aliases.get_wlst_list_path(clusters_location)
        cluster_names = self.wlst_helper.get_existing_object_list(
            cluster_list_path)
        cluster_token = self.aliases.get_name_token(clusters_location)
        # Add the cluster with dynamic servers, if not already in the cluster member list.
        # A cluster may contain both dynamic and configured servers (referred to as mixed cluster).
        # Add a token marking DYNAMIC SERVERS in the member list.
        for cluster_name in cluster_names:
            cluster_location = LocationContext(clusters_location)
            cluster_location.add_name_token(cluster_token, cluster_name)
            cluster_attributes_path = self.aliases.get_wlst_attributes_path(
                cluster_location)
            self.wlst_helper.cd(cluster_attributes_path)
            cluster_location.append_location(DYNAMIC_SERVERS)
            wlst_subfolder_name = self.aliases.get_wlst_mbean_type(
                cluster_location)
            if self.wlst_helper.subfolder_exists(wlst_subfolder_name):
                ds_list_path = self.aliases.get_wlst_list_path(
                    cluster_location)
                ds_names = self.wlst_helper.get_existing_object_list(
                    ds_list_path)
                if len(ds_names) > 0:
                    cluster_location.add_name_token(
                        self.aliases.get_name_token(cluster_location),
                        ds_names[0])
                    cluster_attributes_path = self.aliases.get_wlst_attributes_path(
                        cluster_location)
                    self.wlst_helper.cd(cluster_attributes_path)
                    if self.wlst_helper.get(SERVER_TEMPLATE) is not None:
                        if cluster_name not in cluster_map:
                            cluster_map[cluster_name] = list()
                        cluster_map[cluster_name].append(DYNAMIC_SERVERS)

        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name,
                            result=cluster_map)
        return cluster_map

    def get_existing_server_names(self):
        """
        Get the list of server names from WLST.
        :return: the list of server names
        :raises: BundleAwareException of the specified type: is an error occurs reading from the aliases or WLST
        """
        _method_name = '_get_existing_server_names'

        self.logger.entering(class_name=self.__class_name,
                             method_name=_method_name)
        server_location = LocationContext().append_location(SERVER)
        server_list_path = self.aliases.get_wlst_list_path(server_location)
        result = self.wlst_helper.get_existing_object_list(server_list_path)
        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name,
                            result=result)
        return result

    def get_existing_cluster_names(self):
        """
        Get the list of cluster names from WLST.
        :return: the list of cluster names
        :raises: BundleAwareException of the specified type: is an error occurs reading from the aliases or WLST
        """
        _method_name = 'get_existing_cluster_names'

        self.logger.entering(class_name=self.__class_name,
                             method_name=_method_name)
        cluster_location = LocationContext().append_location(CLUSTER)
        cluster_list_path = self.aliases.get_wlst_list_path(cluster_location)
        result = self.wlst_helper.get_existing_object_list(cluster_list_path)
        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name,
                            result=result)
        return result

    def _get_server_group_targeting_limits(self, server_group_targeting_limits,
                                           clusters_map):
        """
        Get any server group targeting limits specified in the model, converting any cluster
        names to the list of members.  This method assumes that the limits dictionary is not
        None or empty.
        :param server_group_targeting_limits: the raw server group targeting_limits from the model
        :param clusters_map: the map of cluster names to member server names
        :return: the map of server groups to server names to target
        """
        _method_name = '_get_server_group_targeting_limits'

        self.logger.entering(str(server_group_targeting_limits),
                             str(clusters_map),
                             class_name=self.__class_name,
                             method_name=_method_name)
        sg_targeting_limits = copy.deepcopy(server_group_targeting_limits)
        for server_group_name, sg_targeting_limit in sg_targeting_limits.iteritems(
        ):
            if type(sg_targeting_limit) is str:
                if MODEL_LIST_DELIMITER in sg_targeting_limit:
                    sg_targeting_limit = sg_targeting_limit.split(
                        MODEL_LIST_DELIMITER)
                else:
                    # convert a single value into a list of one...
                    new_list = list()
                    new_list.append(sg_targeting_limit)
                    sg_targeting_limit = new_list

            # Convert any references to a cluster name into the list of member server names
            new_list = list()
            for target_name in sg_targeting_limit:
                target_name = target_name.strip()
                if target_name in clusters_map:
                    cluster_members = dictionary_utils.get_element(
                        clusters_map, target_name)
                    if DYNAMIC_SERVERS in cluster_members:
                        # This will need special handling to target server group resources
                        cluster_members.remove(DYNAMIC_SERVERS)
                    new_list.extend(cluster_members)
                else:
                    # Assume it is a server name and add it to the new list
                    # Stand-alone Managed Servers were not added to the cluster: server_name_list map
                    # which was built from the existing servers and clusters.
                    new_list.append(target_name)
            sg_targeting_limits[server_group_name] = new_list

        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name,
                            result=sg_targeting_limits)
        return sg_targeting_limits

    def _get_dynamic_cluster_server_group_targeting_limits(
            self, targeting_limits, clusters_map):
        """
        Get any server group targeting limits specified in the model, converting any cluster
        names to the list of members.  This method assumes that the limits dictionary is not
        None or empty.
        :param targeting_limits: the raw server group targeting_limits from the model
        :param clusters_map: the map of cluster names to member server names
        :return: the map of server groups to server names to target
        """
        _method_name = '_get_dynamic_cluster_server_group_targeting_limits'

        self.logger.entering(str(targeting_limits),
                             str(clusters_map),
                             class_name=self.__class_name,
                             method_name=_method_name)
        dc_sg_targeting_limits = copy.deepcopy(targeting_limits)
        for server_group_name, dc_sg_targeting_limit in dc_sg_targeting_limits.iteritems(
        ):
            if type(dc_sg_targeting_limit) is str:
                if MODEL_LIST_DELIMITER in dc_sg_targeting_limit:
                    dc_sg_targeting_limit = dc_sg_targeting_limit.split(
                        MODEL_LIST_DELIMITER)
                else:
                    # convert a single value into a list of one...
                    new_list = list()
                    new_list.append(dc_sg_targeting_limit)
                    dc_sg_targeting_limit = new_list

            # Convert any references to a cluster name into the list of member server names
            new_list = list()
            for target_name in dc_sg_targeting_limit:
                target_name = target_name.strip()
                if target_name in clusters_map:
                    cluster_members = dictionary_utils.get_element(
                        clusters_map, target_name)
                    if DYNAMIC_SERVERS in cluster_members:
                        # This will need special handling to target server group resources
                        cluster_members.remove(DYNAMIC_SERVERS)
                        cluster_members.append(target_name)
                        new_list.extend(cluster_members)
            dc_sg_targeting_limits[server_group_name] = new_list

        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name,
                            result=dc_sg_targeting_limits)
        return dc_sg_targeting_limits

    def _get_server_to_server_groups_map(self, admin_server_name, server_names,
                                         server_groups, sg_targeting_limits):
        """
        Get the map of server names to the list of server groups to target to that server.
        :param admin_server_name: the admin server name
        :param server_names: the list of server names
        :param server_groups: the complete list of server groups that will, by default, be targeted to
                              all managed servers unless the server is listed in the targeting limits map
        :param sg_targeting_limits: the targeting limits map
        :return: the map of server names to the list of server groups to target to that server
        """
        _method_name = '_get_server_to_server_groups_map'

        self.logger.entering(admin_server_name,
                             str(server_names),
                             str(server_groups),
                             str(sg_targeting_limits),
                             class_name=self.__class_name,
                             method_name=_method_name)
        result = OrderedDict()
        revised_server_groups = self._revised_list_server_groups(
            server_groups, sg_targeting_limits)
        for server_name in server_names:
            server_groups_for_server = self.__get_server_groups_for_entity(
                server_name, sg_targeting_limits)
            if len(server_groups_for_server) > 0:
                result[server_name] = server_groups_for_server
            elif server_name != admin_server_name:
                # By default, we only target managed servers unless explicitly listed in the targeting limits
                result[server_name] = list(revised_server_groups)
            else:
                result[admin_server_name] = list()

        if admin_server_name not in result:
            result[admin_server_name] = list()
        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name,
                            result=result)
        return result

    def get_dc_to_server_groups_map(self, dynamic_cluster_names, server_groups,
                                    dc_sg_targeting_limits):
        """
        Remake the map for each dynamic cluster name and its server groups. If the dynamic cluster is not
        specifically targeted by the dynamic cluster server group targeting limits, targt any remaining
        server groups not in the targeting limits, to any remaining typedef dynamic cluster server group targets.
        :param dynamic_cluster_names: list of dynamic clusters in the domain
        :param server_groups: list of dynamic server groups in the typedef
        :param dc_sg_targeting_limits: list of dynamic cluster to server group targeting limits from the domainInfo
        :return: result: map of dynamic cluster to server groups
        """
        _method_name = 'get_dc_to_server_groups_list'

        self.logger.entering(str(dynamic_cluster_names),
                             str(server_groups),
                             str(dc_sg_targeting_limits),
                             class_name=self.__class_name,
                             method_name=_method_name)
        result = OrderedDict()
        revised_server_groups = self._revised_list_server_groups(
            server_groups, dc_sg_targeting_limits)
        for cluster_name in dynamic_cluster_names:
            server_groups_for_cluster = \
                self.__get_server_groups_for_entity(cluster_name, dc_sg_targeting_limits)
            if len(server_groups_for_cluster) > 0:
                result[cluster_name] = server_groups_for_cluster
            else:
                result[cluster_name] = list(revised_server_groups)
            self.logger.finer('WLSDPLY-12239',
                              result[cluster_name],
                              cluster_name,
                              class_name=self.__class_name,
                              method_name=_method_name)
        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name,
                            result=result)
        return result

    def _revised_list_server_groups(self, server_groups, sg_targeting_limits):
        """
        Remove all server groups that are explicitly targeted to a cluster, server set or stand-alone
        managed server.
        :param server_groups: list of server groups applied by the extension templates
        :param sg_targeting_limits: list of targeting from the domainInfo section
        :return: server group list with the specific targeted server groups removed
        """
        _method_name = '_revised_list_server_groups'
        self.logger.entering(sg_targeting_limits,
                             class_name=self.__class_name,
                             method_name=_method_name)
        result = list()
        targeted_server_groups = sg_targeting_limits.keys()
        for server_group in server_groups:
            if server_group not in targeted_server_groups:
                result.append(server_group)
        return result

    def __get_server_groups_for_entity(self, entity_name, sg_targeting_limits):
        """
        Get the servers groups to target for a given server or dynamic cluster name.
        :param entity_name: the server or dynamic_cluster name
        :param sg_targeting_limits: the targeting limits
        :return: the list of server groups to target to the specified entity name, or None
                 if the entity name does not appear in the targeting limits
        """
        _method_name = '__get_server_groups_for_entity'
        result = list()
        for server_group, entity_names_list in sg_targeting_limits.iteritems():
            if entity_name in entity_names_list:
                result.append(server_group)
        if len(result) > 0:
            self.logger.fine('WLSDPLY-12243',
                             entity_name,
                             result,
                             class_name=self.__class_name,
                             method_name=_method_name)
        return result

    def save_dyn_size(self, cluster_map):
        """
        Collect the before attribute of dynamic cluster size for each dynamic cluster. When
        setting dynamic clusters to server groups, the parameter dynamic cluster size is reset
        based on the parameters in the template server group WSM-CACHE-DYN-CLUSTER. A bug
        was opened 32075458, but probably will be seen as not a bug.
        :param cluster_map: cluster, server groups map
        :return: map of cluster and attribute_value
        """
        _method_name = 'save_dyn_size'
        bug_map = dict()
        for cluster in cluster_map.iterkeys():
            wlst_attribute = self.__locate_dynamic_attribute(cluster)
            bug_map[cluster] = self.wlst_helper.get(wlst_attribute)
            self.logger.finer('WLSDPLY-12560',
                              cluster,
                              bug_map[cluster],
                              class_name=self.__class_name,
                              method_name=_method_name)
        return bug_map

    def restore_dyn_size(self, bug_map):
        """
        The setServerGroups reset the dynamic cluster size. Reset to original value.
        :param bug_map: map with cluster, dynamic cluster size
        """
        _method_name = 'restore_dyn_size'
        self.__put_back_in_edit()
        for cluster, attribute_value in bug_map.iteritems():
            if attribute_value is not None:
                wlst_attribute = self.__locate_dynamic_attribute(cluster)
                self.wlst_helper.set(wlst_attribute, attribute_value)
                self.logger.finer('WLSDPLY-12561',
                                  cluster,
                                  wlst_attribute,
                                  class_name=self.__class_name,
                                  method_name=_method_name)

    def __put_back_in_edit(self):
        """
        setServerGroups throws you out of edit. Put it back in.
        """
        if self.model_context.is_wlst_online():
            try:
                self.wlst_helper.edit()
                self.wlst_helper.start_edit()
            except BundleAwareException, ex:
                raise ex
Exemplo n.º 2
0
class SecurityProviderCreator(Creator):
    """
    The class that drives security provider creation and updates.
    Shared by create domain and update domain.
    """
    __class_name = 'SecurityProviderHelper'

    def __init__(self, model_dictionary, model_context, aliases,
                 exception_type, logger):
        Creator.__init__(self, model_dictionary, model_context, aliases,
                         exception_type, logger)

        self.logger = logger
        self.alias_helper = AliasHelper(aliases, self.logger, exception_type)
        self.wlst_helper = WlstHelper(self.logger, exception_type)
        self.wls_helper = WebLogicHelper(self.logger)

        self._topology = self.model.get_model_topology()

        #
        # Creating domains with the wls.jar template is busted for pre-12.1.2 domains with regards to the
        # names of the default authentication providers (both the DefaultAuthenticator and the
        # DefaultIdentityAsserter names are 'Provider', making it impossible to work with in WLST.  If
        # the WLS version is earlier than fix this as part of domain creation...
        #
        self.__fix_default_authentication_provider_names = \
            self.wls_helper.do_default_authentication_provider_names_need_fixing()
        return

    def create_security_configuration(self, location):
        """
        Create the /SecurityConfiguration folder objects, if any.
        :param location: the location to use
        :raises: BundleAwareException of the specified type: if an error occurs
        """
        _method_name = '__create_security_configuration'

        self.logger.entering(str(location),
                             class_name=self.__class_name,
                             method_name=_method_name)
        security_configuration_nodes = dictionary_utils.get_dictionary_element(
            self._topology, SECURITY_CONFIGURATION)

        self.__handle_default_security_providers(location,
                                                 security_configuration_nodes)
        if len(security_configuration_nodes) > 0:
            self._create_mbean(SECURITY_CONFIGURATION,
                               security_configuration_nodes,
                               location,
                               log_created=True)
        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name)
        return

    def __handle_default_security_providers(self, base_location,
                                            security_configuration_dict):
        _method_name = '__handle_default_security_providers'

        self.logger.entering(str(base_location),
                             class_name=self.__class_name,
                             method_name=_method_name)
        location = self.__get_default_realm_location()
        if security_configuration_dict is None or len(
                security_configuration_dict) == 0:
            if self.__fix_default_authentication_provider_names:
                self.__handle_default_authentication_providers(location)
            self.logger.exiting(class_name=self.__class_name,
                                method_name=_method_name)
            return

        if REALM in security_configuration_dict and 'myrealm' in security_configuration_dict[
                REALM]:
            myrealm = security_configuration_dict[REALM]['myrealm']
            if ADJUDICATOR in myrealm:
                adj_providers = myrealm[ADJUDICATOR]
                self.__handle_default_adjudicators(location, adj_providers)
            if AUDITOR in myrealm:
                audit_providers = myrealm[AUDITOR]
                self.__handle_default_auditors(location, audit_providers)
            if AUTHENTICATION_PROVIDER in myrealm:
                atn_providers = myrealm[AUTHENTICATION_PROVIDER]
                self.__handle_default_authentication_providers(
                    location, atn_providers)
            elif self.__fix_default_authentication_provider_names:
                self.__handle_default_authentication_providers(location)
            if AUTHORIZER in myrealm:
                atz_providers = myrealm[AUTHORIZER]
                self.__handle_default_authorizers(location, atz_providers)
            if CERT_PATH_PROVIDER in myrealm:
                cert_path_providers = myrealm[CERT_PATH_PROVIDER]
                self.__handle_default_cert_path_providers(
                    location, cert_path_providers)
            if CREDENTIAL_MAPPER in myrealm:
                credential_mapping_providers = myrealm[CREDENTIAL_MAPPER]
                self.__handle_default_credential_mappers(
                    location, credential_mapping_providers)
            if PASSWORD_VALIDATOR in myrealm:
                password_validation_providers = myrealm[PASSWORD_VALIDATOR]
                self.__handle_default_password_validators(
                    location, password_validation_providers)
            if ROLE_MAPPER in myrealm:
                role_mapping_providers = myrealm[ROLE_MAPPER]
                self.__handle_default_role_mappers(location,
                                                   role_mapping_providers)
        elif self.__fix_default_authentication_provider_names:
            self.__handle_default_authentication_providers(location)

        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name)
        return

    def __get_default_realm_location(self):
        """
        Ensure that the default realm exists and get the location object for it.
        :return: the location object to use to work on the default realm while creating a domain.
        """
        location = LocationContext().append_location(SECURITY_CONFIGURATION)

        # SecurityConfiguration is special since the subfolder name does not change when
        # you change the domain name.  It only changes once the domain is written and re-read...
        token_name = self.alias_helper.get_name_token(location)
        if token_name is not None:
            existing_names = deployer_utils.get_existing_object_list(
                location, self.alias_helper)
            if len(existing_names) > 0:
                domain_name = existing_names[0]
                location.add_name_token(token_name, domain_name)

        wlst_create_path = self.alias_helper.get_wlst_create_path(location)
        self.wlst_helper.cd(wlst_create_path)
        existing_folder_names = self.wlst_helper.get_existing_object_list(
            wlst_create_path)

        wlst_type, wlst_name = self.alias_helper.get_wlst_mbean_type_and_name(
            location)
        wlst_attribute_path = self.alias_helper.get_wlst_attributes_path(
            location)
        if wlst_type not in existing_folder_names:
            self.wlst_helper.create_and_cd(self.alias_helper, wlst_type,
                                           wlst_name, location,
                                           wlst_create_path)
        else:
            self.wlst_helper.cd(wlst_attribute_path)

        existing_folder_names = self.wlst_helper.get_existing_object_list(
            wlst_attribute_path)
        location.append_location(REALM)
        wlst_type = self.alias_helper.get_wlst_mbean_type(location)
        token_name = self.alias_helper.get_name_token(location)

        if wlst_type not in existing_folder_names:
            self.__default_security_realm_name = self.wls_helper.get_default_security_realm_name(
            )
            if token_name is not None:
                location.add_name_token(token_name,
                                        self.__default_security_realm_name)
            wlst_name = self.alias_helper.get_wlst_mbean_name(location)
            self.wlst_helper.create_and_cd(self.alias_helper, wlst_type,
                                           wlst_name, location)
        else:
            wlst_list_path = self.alias_helper.get_wlst_list_path(location)
            existing_folder_names = self.wlst_helper.get_existing_object_list(
                wlst_list_path)
            if len(existing_folder_names) > 0:
                self.__default_security_realm_name = existing_folder_names[0]
            if token_name is not None:
                location.add_name_token(token_name,
                                        self.__default_security_realm_name)
            wlst_attribute_path = self.alias_helper.get_wlst_attributes_path(
                location)
            self.wlst_helper.cd(wlst_attribute_path)
        return location

    def __handle_default_adjudicators(self, base_location, adj_providers):
        if adj_providers is None or len(
                adj_providers) == 0 or DEFAULT_ADJUDICATOR_NAME is None:
            return

        if self.__need_to_delete_default_provider(adj_providers,
                                                  DEFAULT_ADJUDICATOR_NAME,
                                                  DEFAULT_ADJUDICATOR_TYPE):
            self.__delete_provider(base_location, DEFAULT_ADJUDICATOR_NAME,
                                   ADJUDICATOR)
        return

    def __handle_default_auditors(self, base_location, audit_providers):
        if audit_providers is None or len(
                audit_providers) == 0 or DEFAULT_AUDITOR_NAME is None:
            return

        if self.__need_to_delete_default_provider(audit_providers,
                                                  DEFAULT_AUDITOR_NAME,
                                                  DEFAULT_AUDITOR_TYPE):
            self.__delete_provider(base_location, DEFAULT_AUDITOR_NAME,
                                   AUDITOR)
        return

    def __handle_default_authentication_providers(self,
                                                  base_location,
                                                  atn_providers=None):
        _method_name = '__handle_default_authentication_providers'

        self.logger.entering(str(base_location),
                             class_name=self.__class_name,
                             method_name=_method_name)
        if atn_providers is None or len(atn_providers) == 0 or \
                (DEFAULT_AUTHENTICATOR_NAME is None and DEFAULT_IDENTITY_ASSERTER_NAME is None):
            if self.__fix_default_authentication_provider_names:
                # delete and recreate the default authenticator and default identity asserter with the correct names.
                self.__delete_and_recreate_provider(
                    base_location, 'Provider', DEFAULT_AUTHENTICATOR_NAME,
                    AUTHENTICATION_PROVIDER, DEFAULT_AUTHENTICATOR_TYPE)
                self.__delete_and_recreate_provider(
                    base_location, 'Provider', DEFAULT_IDENTITY_ASSERTER_NAME,
                    AUTHENTICATION_PROVIDER, DEFAULT_IDENTITY_ASSERTER_TYPE)
                self.__set_default_identity_asserter_attributes(
                    base_location, DEFAULT_IDENTITY_ASSERTER_NAME,
                    AUTHENTICATION_PROVIDER, DEFAULT_IDENTITY_ASSERTER_TYPE)
            self.logger.exiting(class_name=self.__class_name,
                                method_name=_method_name)
            return

        atn_names = atn_providers.keys()
        if atn_names[0] == DEFAULT_AUTHENTICATOR_NAME:
            default_authenticator = atn_providers[DEFAULT_AUTHENTICATOR_NAME]
            type_keys = default_authenticator.keys()
            if len(type_keys) == 0 or (len(type_keys) == 1 and type_keys[0]
                                       == DEFAULT_AUTHENTICATOR_TYPE):
                delete_default_authenticator = False
            else:
                delete_default_authenticator = True
        else:
            delete_default_authenticator = True

        if len(atn_names
               ) > 1 and atn_names[1] == DEFAULT_IDENTITY_ASSERTER_NAME:
            default_identity_asserter = atn_providers
            type_keys = default_identity_asserter.keys()
            if len(type_keys) == 0 or (len(type_keys) == 1 and type_keys[0]
                                       == DEFAULT_IDENTITY_ASSERTER_TYPE):
                delete_default_identity_asserter = False
            else:
                delete_default_identity_asserter = True
        else:
            delete_default_identity_asserter = True

        if delete_default_authenticator:
            if self.__fix_default_authentication_provider_names:
                name = 'Provider'
            else:
                name = DEFAULT_AUTHENTICATOR_NAME
            self.__delete_provider(base_location, name,
                                   AUTHENTICATION_PROVIDER)
        elif self.__fix_default_authentication_provider_names:
            # delete and recreate the default authenticator with the correct name now.
            self.__delete_and_recreate_provider(base_location, 'Provider',
                                                DEFAULT_AUTHENTICATOR_NAME,
                                                AUTHENTICATION_PROVIDER,
                                                DEFAULT_AUTHENTICATOR_TYPE)

        if delete_default_identity_asserter:
            if self.__fix_default_authentication_provider_names:
                name = 'Provider'
            else:
                name = DEFAULT_IDENTITY_ASSERTER_NAME
            self.__delete_provider(base_location, name,
                                   AUTHENTICATION_PROVIDER)
            self.__fix_up_model_default_identity_asserter(
                base_location, DEFAULT_IDENTITY_ASSERTER_NAME,
                AUTHENTICATION_PROVIDER, DEFAULT_IDENTITY_ASSERTER_TYPE,
                atn_providers)
        elif self.__fix_default_authentication_provider_names:
            # delete and recreate the default identity asserter with the correct name now.
            self.__delete_and_recreate_provider(
                base_location, 'Provider', DEFAULT_IDENTITY_ASSERTER_NAME,
                AUTHENTICATION_PROVIDER, DEFAULT_IDENTITY_ASSERTER_TYPE)
            self.__set_default_identity_asserter_attributes(
                base_location, DEFAULT_IDENTITY_ASSERTER_NAME,
                AUTHENTICATION_PROVIDER, DEFAULT_IDENTITY_ASSERTER_TYPE)

        self.logger.exiting(class_name=self.__class_name,
                            method_name=_method_name)
        return

    def __handle_default_authorizers(self, base_location,
                                     authorization_providers):
        if authorization_providers is None or len(
                authorization_providers
        ) == 0 or DEFAULT_AUTHORIZER_NAME is None:
            return

        if self.__need_to_delete_default_provider(authorization_providers,
                                                  DEFAULT_AUTHORIZER_NAME,
                                                  DEFAULT_AUTHORIZER_TYPE):
            self.__delete_provider(base_location, DEFAULT_AUTHORIZER_NAME,
                                   AUTHORIZER)
        return

    def __handle_default_cert_path_providers(self, base_location,
                                             cert_path_providers):
        if cert_path_providers is None or len(
                cert_path_providers
        ) == 0 or DEFAULT_CERT_PATH_PROVIDER_NAME is None:
            return

        if self.__need_to_delete_default_provider(
                cert_path_providers, DEFAULT_CERT_PATH_PROVIDER_NAME,
                DEFAULT_CERT_PATH_PROVIDER_TYPE):
            self.__delete_provider(base_location,
                                   DEFAULT_CERT_PATH_PROVIDER_NAME,
                                   CERT_PATH_PROVIDER)
        return

    def __handle_default_credential_mappers(self, base_location,
                                            credential_mapping_providers):
        if credential_mapping_providers is None or len(credential_mapping_providers) == 0 or \
                DEFAULT_CREDENTIAL_MAPPER_NAME is None:
            return

        if self.__need_to_delete_default_provider(
                credential_mapping_providers, DEFAULT_CREDENTIAL_MAPPER_NAME,
                DEFAULT_CREDENTIAL_MAPPER_TYPE):
            self.__delete_provider(base_location,
                                   DEFAULT_CREDENTIAL_MAPPER_NAME,
                                   CREDENTIAL_MAPPER)
        return

    def __handle_default_password_validators(self, base_location,
                                             password_validation_providers):
        if password_validation_providers is None or len(password_validation_providers) == 0 or \
                DEFAULT_PASSWORD_VALIDATOR_NAME is None:
            return

        if self.__need_to_delete_default_provider(
                password_validation_providers, DEFAULT_PASSWORD_VALIDATOR_NAME,
                DEFAULT_PASSWORD_VALIDATOR_TYPE):
            self.__delete_provider(base_location,
                                   DEFAULT_PASSWORD_VALIDATOR_NAME,
                                   PASSWORD_VALIDATOR)
        return

    def __handle_default_role_mappers(self, base_location,
                                      role_mapping_providers):
        if role_mapping_providers is None or len(
                role_mapping_providers
        ) == 0 or DEFAULT_ROLE_MAPPER_NAME is None:
            return

        if self.__need_to_delete_default_provider(role_mapping_providers,
                                                  DEFAULT_ROLE_MAPPER_NAME,
                                                  DEFAULT_ROLE_MAPPER_TYPE):
            self.__delete_provider(base_location, DEFAULT_ROLE_MAPPER_NAME,
                                   ROLE_MAPPER)
        return

    def __need_to_delete_default_provider(self, providers_dict, default_name,
                                          default_type):
        provider_names = providers_dict.keys()
        if provider_names[0] == default_name:
            default_provider = providers_dict[default_name]
            type_keys = default_provider.keys()
            if len(type_keys) == 0 or (len(type_keys) == 1
                                       and type_keys[0] == default_type):
                delete_default_provider = False
            else:
                delete_default_provider = True
        else:
            delete_default_provider = True
        return delete_default_provider

    def __delete_provider(self, base_location, model_name, model_base_type):
        location = LocationContext(base_location).append_location(
            model_base_type)
        token_name = self.alias_helper.get_name_token(location)
        if token_name is not None:
            location.add_name_token(token_name, model_name)

        wlst_create_path = self.alias_helper.get_wlst_create_path(location)
        wlst_type, wlst_name = self.alias_helper.get_wlst_mbean_type_and_name(
            location)
        self.wlst_helper.cd(wlst_create_path)
        self.wlst_helper.delete(wlst_name, wlst_type)
        return

    def __delete_and_recreate_provider(self, base_location, old_wlst_name,
                                       model_name, model_base_type,
                                       model_subtype):
        self.__delete_provider(base_location, old_wlst_name, model_base_type)

        location = LocationContext(base_location).append_location(
            model_base_type)
        token_name = self.alias_helper.get_name_token(location)
        if token_name is not None:
            location.add_name_token(token_name, model_name)

        wlst_create_path = self.alias_helper.get_wlst_create_path(location)
        wlst_base_type, wlst_name = self.alias_helper.get_wlst_mbean_type_and_name(
            location)
        location.append_location(model_subtype)
        wlst_type = self.alias_helper.get_wlst_mbean_type(location)
        self.wlst_helper.cd(wlst_create_path)
        self.wlst_helper.create(wlst_name, wlst_type, wlst_base_type)
        return

    def __set_default_identity_asserter_attributes(self, base_location,
                                                   model_name, model_base_type,
                                                   model_subtype):
        location = LocationContext(base_location).append_location(
            model_base_type)
        token_name = self.alias_helper.get_name_token(location)
        if token_name is not None:
            location.add_name_token(token_name, model_name)
        location.append_location(model_subtype)

        wlst_attribute_path = self.alias_helper.get_wlst_attributes_path(
            location)
        default_value = self.alias_helper.get_model_attribute_default_value(
            location, ACTIVE_TYPE)
        wlst_name = self.alias_helper.get_wlst_attribute_name(
            location, ACTIVE_TYPE)
        self.wlst_helper.cd(wlst_attribute_path)
        self.wlst_helper.set(wlst_name, default_value)
        return

    #
    # Since we are allowing the provider to be recreated, if needed, from the model,
    # we need to add the ActiveType attribute to the model if and only if no
    # attributes are specified in the model.
    #
    def __fix_up_model_default_identity_asserter(self, base_location,
                                                 model_name, model_base_type,
                                                 model_subtype, atn_providers):
        if atn_providers is not None and DEFAULT_IDENTITY_ASSERTER_NAME in atn_providers:
            default_identity_asserter = \
                dictionary_utils.get_dictionary_element(atn_providers, DEFAULT_IDENTITY_ASSERTER_NAME)
            if DEFAULT_IDENTITY_ASSERTER_TYPE in default_identity_asserter:
                subtype_dict = dictionary_utils.get_dictionary_element(
                    default_identity_asserter, DEFAULT_IDENTITY_ASSERTER_TYPE)
                if len(subtype_dict) == 0:
                    location = LocationContext(base_location).append_location(
                        model_base_type)
                    token_name = self.alias_helper.get_name_token(location)
                    if token_name is not None:
                        location.add_name_token(token_name, model_name)
                    location.append_location(model_subtype)

                    default_value = self.alias_helper.get_model_attribute_default_value(
                        location, ACTIVE_TYPE)
                    subtype_dict[ACTIVE_TYPE] = default_value
        return
class TopologyHelper(object):
    """
    Shared code for topology section of model. Domain create and update use this code.
    """
    __class_name = 'TopologyHelper'

    def __init__(self, aliases, exception_type, logger):
        self.logger = logger
        self.aliases = aliases
        self.wlst_helper = WlstHelper(exception_type)

        self._coherence_cluster_elements = [CLUSTER, SERVER, SERVER_TEMPLATE]

    def check_coherence_cluster_references(self, type_name, model_nodes):
        """
        If the specified type has a Coherence cluster system resource attribute, verify that any referenced resource
        exists. If the resource does not exist, create an empty placeholder resource to allow assignment.
        :param type_name: the model folder type
        :param model_nodes: a dictionary containing the named model elements
        :raises: BundleAwareException of the specified type: if an error occurs
        """
        if type_name in self._coherence_cluster_elements:
            for name in model_nodes.keys():
                child_nodes = dictionary_utils.get_dictionary_element(model_nodes, name)
                resource_name = dictionary_utils.get_element(child_nodes, COHERENCE_CLUSTER_SYSTEM_RESOURCE)
                if resource_name is not None:
                    self._create_placeholder_coherence_cluster(resource_name)

    def _create_placeholder_coherence_cluster(self, cluster_name):
        """
        Create a placeholder Coherence cluster system resource to be referenced from a topology element.
        The new cluster will be created at the root domain level.
        :param cluster_name: the name of the Coherence cluster system resource to be added
        """
        _method_name = '_create_placeholder_coherence_cluster'
        original_location = self.wlst_helper.get_pwd()
        cluster_location = LocationContext().append_location(COHERENCE_CLUSTER_SYSTEM_RESOURCE)
        existing_names = deployer_utils.get_existing_object_list(cluster_location, self.aliases)

        if cluster_name not in existing_names:
            self.logger.info('WLSDPLY-12230', cluster_name, class_name=self.__class_name, method_name=_method_name)

            cluster_token = self.aliases.get_name_token(cluster_location)
            cluster_location.add_name_token(cluster_token, cluster_name)
            deployer_utils.create_and_cd(cluster_location, existing_names, self.aliases)

        self.wlst_helper.cd(original_location)

    def create_placeholder_servers_in_cluster(self, topology):
        """
        Create a placeholder for servers that are in a cluster, as these are migratable entities that
        can reference other servers in the cluster.
        :param topology: The topology model nodes containing the full set of Servers to add for the create / update
        """
        _method_name = 'create_placeholder_servers_in_cluster'
        self.logger.entering(class_name=self.__class_name, method_name=_method_name)
        self.create_placeholder_named_elements(LocationContext(), SERVER, topology)
        self.logger.exiting(class_name=self.__class_name, method_name=_method_name)

    def create_placeholder_server_templates(self, topology):
        """
        Create a placeholder server template for each name in the topology.
        This is necessary because there is a circular dependency between clusters and server templates.
        :param topology: the topology model nodes
        """
        self.create_placeholder_named_elements(LocationContext(), SERVER_TEMPLATE, topology)

    def create_placeholder_jdbc_resources(self, resources):
        """
        Create a placeholder JDBC resource for each name in the resources section.
        This is necessary because cluster attributes may reference JDBC resources.
        :param resources: the resource model nodes
        :return: a list of names of created placeholders
        """
        return self.create_placeholder_named_elements(LocationContext(), JDBC_SYSTEM_RESOURCE, resources)

    def create_placeholder_named_elements(self, location, model_type, model_nodes):
        """
        Create a placeholder entry for each element in the specified named element nodes.
        This is necessary when there can be circular references with other elements.
        :param location: the location for the nodes to be added
        :param model_type: the type of the specified model nodes
        :param model_nodes: the model nodes
        :return: a list of names of created placeholders
        """
        _method_name = 'create_placeholder_named_elements'
        holder_names = []
        original_location = self.wlst_helper.get_pwd()
        resource_location = LocationContext(location).append_location(model_type)

        if self.aliases.get_wlst_mbean_type(resource_location) is not None:
            existing_names = deployer_utils.get_existing_object_list(resource_location, self.aliases)

            name_nodes = dictionary_utils.get_dictionary_element(model_nodes, model_type)
            for name in name_nodes.keys():
                if model_helper.is_delete_name(name):
                    # don't create placeholder for delete names
                    continue

                if name not in existing_names:
                    self.logger.info('WLSDPLY-19403', model_type, name, class_name=self.__class_name,
                                     method_name=_method_name)

                    token = self.aliases.get_name_token(resource_location)
                    resource_location.add_name_token(token, name)
                    deployer_utils.create_and_cd(resource_location, existing_names, self.aliases)
                    self._update_placeholder(model_type, name, resource_location)
                    holder_names.append(name)

        self.wlst_helper.cd(original_location)
        return holder_names

    def _update_placeholder(self, type_name, name, location):
        """
        Make any required updates to a newly-created placeholder.
        :param type_name: the type name of the placeholder
        :param name: the name of the placeholder MBean
        :param location: the location of the placeholder
        """
        if type_name == JDBC_SYSTEM_RESOURCE:
            # for online update, Name must be assigned to each JDBCSystemResource / JdbcResource MBean.
            # (see datasource_deployer.set_attributes())
            child_location = LocationContext(location).append_location(JDBC_RESOURCE)
            deployer_utils.set_single_folder_token(child_location, self.aliases)
            wlst_path = self.aliases.get_wlst_attributes_path(child_location)
            if self.wlst_helper.path_exists(wlst_path):
                original_location = self.wlst_helper.get_pwd()
                self.wlst_helper.cd(wlst_path)
                existing_name = self.wlst_helper.get('Name')
                if existing_name is None:
                    self.wlst_helper.set('Name', name)
                self.wlst_helper.cd(original_location)

    def clear_jdbc_placeholder_targeting(self, jdbc_names):
        """
        Remove any targets for the JDBC resources in the specified list of names.
        Targets may have been inadvertently assigned when clusters were added after JDBC placeholders.
        :param jdbc_names: names of placeholders to clear
        """
        _method_name = 'clear_jdbc_placeholder_targeting'
        resource_location = LocationContext().append_location(JDBC_SYSTEM_RESOURCE)
        token = self.aliases.get_name_token(resource_location)

        for name in jdbc_names:
            self.logger.info('WLSDPLY-19404', JDBC_SYSTEM_RESOURCE, name, class_name=self.__class_name,
                             method_name=_method_name)

            resource_location.add_name_token(token, name)
            wlst_path = self.aliases.get_wlst_attributes_path(resource_location)
            if self.wlst_helper.path_exists(wlst_path):
                mbean = self.wlst_helper.get_mbean_for_wlst_path(wlst_path)
                mbean.setTargets(None)

    def qualify_nm_properties(self, type_name, model_nodes, base_location, model_context, attribute_setter):
        """
        For the NM properties MBean, update the keystore file path to be fully qualified with the domain directory.
        :param type_name: the type name of the MBean to be checked
        :param model_nodes: the model nodes of the MBean to be checked
        :param base_location: the parent location of the MBean
        :param model_context: the model context of the tool
        :param attribute_setter: the attribute setter to be used for update
        """
        if type_name == NM_PROPERTIES:
            location = LocationContext(base_location).append_location(type_name)
            keystore_file = dictionary_utils.get_element(model_nodes, CUSTOM_IDENTITY_KEYSTORE_FILE)
            if keystore_file and WLSDeployArchive.isPathIntoArchive(keystore_file):
                value = model_context.get_domain_home() + "/" + keystore_file
                attribute_setter.set_attribute(location, CUSTOM_IDENTITY_KEYSTORE_FILE, value)

    def is_clustered_server(self, server_name, servers_dictionary):
        """
        Return true if the server's Cluster attribute is set.
        :param server_name: name of the server in the dictionary
        :param servers_dictionary: model topology section of servers
        :return: True if a clustered server
        """
        server_dictionary = dictionary_utils.get_dictionary_element(servers_dictionary, server_name)
        if dictionary_utils.is_empty_dictionary_element(server_dictionary, CLUSTER):
            return False
        return True
class AttributeSetter(object):
    """
    Contains the "set" methods used to set WLST values that require mbeans or other special processing.
    The public set_ methods in this class correspond directly to the set_method names in the alias files.
    The signature for each set_ method is (location, key, value), where key and value are from the model.
    """

    # used for target search
    __target_type_names = [
        CLUSTER,
        SERVER,
        MIGRATABLE_TARGET,
    ]

    # used for destination search
    __destination_type_names = [
        QUEUE,
        TOPIC,
        DISTRIBUTED_QUEUE,
        DISTRIBUTED_TOPIC,
        UNIFORM_DISTRIBUTED_QUEUE,
        UNIFORM_DISTRIBUTED_TOPIC
    ]

    # used for SAF destination search
    __saf_destination_type_names = [
        SAF_QUEUE,
        SAF_TOPIC
    ]

    # used for persistent store search
    __persistent_store_type_names = [
        FILE_STORE,
        JDBC_STORE
    ]

    # used for self-tuning deployment and attribute processing.
    # these names are applicable as self-tuning sub-folder names, and work manager attribute names.
    # work manager is intentionally excluded and treated separately.
    __self_tuning_type_names = [
        CAPACITY,
        CONTEXT_REQUEST_CLASS,
        FAIR_SHARE_REQUEST_CLASS,
        MAX_THREADS_CONSTRAINT,
        MIN_THREADS_CONSTRAINT,
        RESPONSE_TIME_REQUEST_CLASS
    ]

    # used for WLDF watch notification search
    __watch_action_types = [
        HEAP_DUMP_ACTION,
        IMAGE_NOTIFICATION,
        JMS_NOTIFICATION,
        JMX_NOTIFICATION,
        LOG_ACTION,
        REST_NOTIFICATION,
        SCRIPT_ACTION,
        SMTP_NOTIFICATION,
        SNMP_NOTIFICATION,
        THREAD_DUMP_ACTION
    ]

    _class_name = "AttributeSetter"

    def __init__(self, aliases, logger, exception_type, wlst_mode=WlstModes.OFFLINE):
        self.__logger = logger
        self.__exception_type = exception_type
        self.__wlst_mode = wlst_mode
        self.__alias_helper = AliasHelper(aliases, self.__logger, exception_type)
        self.__wlst_helper = WlstHelper(self.__logger, exception_type)
        return

    #
    # public set_ methods for special attribute types, signature (self, location, key, value, wlst_value, ...)
    #

    def set_target_jms_mbeans(self, location, key, value, wlst_value):
        """
        Set the target MBeans for targets that can include JMS resources (e.g., JMSServer).
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if target is not found
        """
        self.set_target_mbeans(location, key, value, wlst_value, include_jms=True)
        return

    def set_target_mbeans(self, location, key, value, wlst_value, include_jms=False):
        """
        Set the target MBeans.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :param include_jms: whether or not to include JMS resources
        :raises BundleAwareException of the specified type: if target is not found
        """
        targets_value = self.__build_target_mbean_list(value, wlst_value, location, include_jms=include_jms)
        self.set_attribute(location, key, targets_value, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_jms_error_destination_mbean(self, location, key, value, wlst_value):
        """
        Set the JMS Error Destination MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if destination is not found
        """
        mbean = self.__find_jms_destination_mbean(location, value)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_jms_bridge_destination_mbean(self, location, key, value, wlst_value):
        """
        Set the JMS Bridge Destination MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if destination is not found
        """
        mbean = self.__find_in_resource_group_or_domain(location, JMS_BRIDGE_DESTINATION, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_persistent_store_mbean(self, location, key, value, wlst_value):
        """
        Set the Persistent Store MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if store is not found
        """
        mbean = self.__find_persistent_store(location, value)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_data_source_mbean(self, location, key, value, wlst_value):
        """
        Set the DataSource MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if DataSource is not found
        """
        mbean = self.__find_in_resource_group_or_domain(location, JDBC_SYSTEM_RESOURCE, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_saf_remote_context_mbean(self, location, key, value, wlst_value):
        """
        Set the SAF RemoteContext MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if SAF RemoteContext is not found
        """
        resource_location = self.__get_parent_location(location, JMS_RESOURCE)
        mbean = self.__find_in_location(resource_location, SAF_REMOTE_CONTEXT, value)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_saf_error_destination_mbean(self, location, key, value, wlst_value):
        """
        Set the SAF Error Destination MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if destination is not found
        """
        mbean = self.__find_saf_destination_mbean(location, value)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_self_tuning_mbean(self, location, key, value, wlst_value):
        """
        Set the SelfTuning MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if MBean is not found
        """
        tuning_location = self.__get_parent_location(location, SELF_TUNING)
        mbean = self.__find_in_location(tuning_location, key, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_server_mbeans(self, location, key, value, wlst_value):
        """
        Set the Server MBeans.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if a server is not found
        """
        mbeans = self.__build_server_mbean_list(value, wlst_value)
        self.set_attribute(location, key, mbeans, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_server_mbean(self, location, key, value, wlst_value):
        """
        Set the Server MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if the server is not found
        """
        mbean = self.__find_in_location(LocationContext(), SERVER, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_server_template_mbean(self, location, key, value, wlst_value):
        """
        Set the Server Template MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if the server template is not found
        """
        mbean = self.__find_in_location(LocationContext(), SERVER_TEMPLATE, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_cluster_mbean(self, location, key, value, wlst_value):
        """
        Set the Cluster MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if the cluster is not found
        """
        mbean = self.__find_in_location(LocationContext(), CLUSTER, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_server_cluster_mbean(self, location, key, value, wlst_value):
        """
        assign the Cluster MBean to a server.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if the cluster is not found
        """

        entity_type, entity_name = self.__alias_helper.get_model_type_and_name(location)

        self.__wlst_helper.assign(entity_type, entity_name, key, value)
        return

    def set_coherence_cluster_mbean(self, location, key, value, wlst_value):
        """
        Set the Log Filter MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if store is not found
        """
        mbean = self.__find_in_location(LocationContext(), COHERENCE_CLUSTER_SYSTEM_RESOURCE, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_machine_mbean(self, location, key, value, wlst_value):
        """
        Set the Machine MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if the machine is not found
        """
        mbean = self.__find_in_location(LocationContext(), MACHINE, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_jms_template_mbean(self, location, key, value, wlst_value):
        """
        Set the JMS Template MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if the JMS Template is not found
        """
        resource_location = self.__get_parent_location(location, JMS_RESOURCE)
        mbean = self.__find_in_location(resource_location, TEMPLATE, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_wldf_action_mbeans(self, location, key, value, wlst_value):
        """
        Set the WLDF Action/Notification MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if the WLDF Action/Notification is not found
        """
        watch_location = self.__get_parent_location(location, WATCH_NOTIFICATION)
        action_names = TypeUtils.convertToType(List, value)  # type: list of str
        action_names = self.__merge_existing_items(action_names, wlst_value)

        action_mbeans = []
        for action_name in action_names:
            action_mbean = self.__find_wldf_action(watch_location, action_name)
            action_mbeans.append(action_mbean)

        if self.__wlst_mode == WlstModes.ONLINE:
            # for online, call the current location's add method for each action mbean
            location_mbean = self.__wlst_helper.cd(self.__wlst_helper.get_pwd())
            for action_mbean in action_mbeans:
                location_mbean.addNotification(action_mbean)
        else:
            self.set_attribute(location, key, action_mbeans, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_log_filter_mbean(self, location, key, value, wlst_value):
        """
        Set the Log Filter MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if store is not found
        """
        mbean = self.__find_in_location(LocationContext(), LOG_FILTER, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_jms_server_mbean(self, location, key, value, wlst_value):
        """
        For those entities, such as WLSReliableDeliveryPolicy, that take a single JMS Server mbean.
        :param location: location to look for jms server
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if jms server mbean is not found.
        """
        mbean = self.__find_in_location(LocationContext(), JMS_SERVER, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_jms_quota_mbean(self, location, key, value, wlst_value):
        """
        For those entities, queues, template, topics, that take a single Quota mbean.
        :param location: location to look for Quota mbean
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if quota mbean is not found.
        """
        resource_location = self.__get_parent_location(location, JMS_RESOURCE)
        mbean = self.__find_in_location(resource_location, QUOTA, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_reliable_delivery_policy_mbean(self, location, key, value, wlst_value):
        """
        Sets the ws soap reliable delivery policy mbean used by mbeans like Server and Server Template.
        :param location: location to look for reliable delivery policy
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if reliable delivery policy mbean is not found.
        """
        mbean = self.__find_in_location(LocationContext(), WS_RELIABLE_DELIVERY_POLICY, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_xml_entity_cache_mbean(self, location, key, value, wlst_value):
        """
        Sets the XML cache mbean used by topology entities such as Server.
        :param location: location to look for reliable delivery policy
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if xml entity cache mbean is not found.
        """
        mbean = self.__find_in_location(LocationContext(), XML_ENTITY_CACHE, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_xml_registry_mbean(self, location, key, value, wlst_value):
        """
        Sets the XML registry mbean used by topology entities such as Server.
        :param location: location to look for reliable delivery policy
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if xml registry mbean is not found.
        """
        mbean = self.__find_in_location(LocationContext(), XML_REGISTRY, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_mt_target_mbeans(self, location, key, value, wlst_value):
        """
        Set the virtual target MBeans.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if target is not found
        """
        targets_value = self.__build_virtual_target_mbean_list(value, wlst_value)
        self.set_attribute(location, key, targets_value, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_partition_security_realm(self, location, key, value, wlst_value):
        """
        Set the security realm MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if target is not found
        """
        security_location = self.__get_domain_location(location).append_location(SECURITY_CONFIGURATION)
        mbean = self.__find_in_location(security_location, REALM, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_resource_group_template_mbean(self, location, key, value, wlst_value):
        """
        Set the resource group template MBean.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if target is not found
        """
        domain_location = self.__get_domain_location(location)
        mbean = self.__find_in_location(domain_location, RESOURCE_GROUP_TEMPLATE, value, required=True)
        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_partition_work_manager_mbean(self, location, key, value, wlst_value):
        """
        Set the partition work manager MBean. Search in the same partition, then at the domain level.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if target is not found
        """
        method_name = 'set_partition_work_manager_mbean'
        partition_location = self.__get_parent_location(location, PARTITION)
        mbean = self.__find_in_location(partition_location, PARTITION_WORK_MANAGER, value)
        if mbean is None:
            domain_location = self.__get_domain_location(location)
            mbean = self.__find_in_location(domain_location, PARTITION_WORK_MANAGER, value)

        if mbean is None:
            _type, partition_name = self.__alias_helper.get_model_type_and_name(location)
            ex = exception_helper.create_exception(self.__exception_type, 'WLSDPLY-19206', value, partition_name)
            self.__logger.throwing(class_name=self._class_name, method_name=method_name, error=ex)
            raise ex

        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    def set_resource_manager_mbean(self, location, key, value, wlst_value):
        """
        Set the resource manager MBean. Search in the same partition, then at the domain level.
        :param location: the location
        :param key: the attribute name
        :param value: the string value
        :param wlst_value: the existing value of the attribute from WLST
        :raises BundleAwareException of the specified type: if target is not found
        """
        method_name = 'set_resource_manager_mbean'
        partition_location = self.__get_parent_location(location, PARTITION)
        mbean = self.__find_in_location(partition_location, RESOURCE_MANAGER, value)
        if mbean is None:
            management_location = self.__get_domain_location(location).append_location(RESOURCE_MANAGEMENT)
            mbean = self.__find_in_location(management_location, RESOURCE_MANAGER, value)

        if mbean is None:
            _type, manager_name = self.__alias_helper.get_model_type_and_name(location)
            ex = exception_helper.create_exception(self.__exception_type, 'WLSDPLY-19207', value, manager_name)
            self.__logger.throwing(class_name=self._class_name, method_name=method_name, error=ex)
            raise ex

        self.set_attribute(location, key, mbean, wlst_merge_value=wlst_value, use_raw_value=True)
        return

    #
    # public set_attribute convenience methods
    #

    def set_attribute(self, location, model_key, model_value, wlst_merge_value=None, use_raw_value=False):
        """
        Convenience method for setting the attribute.
        :param location: location
        :param model_key: attribute name
        :param model_value: attribute value
        :param wlst_merge_value: value from WLST to merge
        :param use_raw_value: whether or not to the use the model value, default is to use the WLST value
        :raises BundleAwareException of the specified type: if an error occurs
        """
        _method_name = 'set_attribute'

        if use_raw_value:
            wlst_param = self.__alias_helper.get_wlst_attribute_name(location, model_key)
            wlst_value = model_value
        else:
            wlst_param, wlst_value = \
                self.__alias_helper.get_wlst_attribute_name_and_value(location, model_key, model_value,
                                                                      existing_wlst_value=wlst_merge_value)

        if wlst_param is None:
            self.__logger.info('WLSDPLY-20011', model_key, class_name=self._class_name, method_name=_method_name)
        elif wlst_value is None:
            self.__logger.info('WLSDPLY-20012', model_key, str(model_value),
                               class_name=self._class_name, method_name=_method_name)
        else:
            self.__wlst_helper.set(wlst_param, wlst_value)
        return

    def set_attribute_with_cmo(self, location, key, value, wlst_value=None, masked=False):
        _method_name = 'set_attribute_with_cmo'

        wlst_attr_name, wlst_attr_value = \
            self.__alias_helper.get_wlst_attribute_name_and_value(location, key, value, existing_wlst_value=wlst_value)

        if wlst_attr_name is None:
            self.__logger.info('WLSDPLY-20011', key, class_name=self._class_name, method_name=_method_name)
        elif wlst_attr_value is None:
            log_value = str(value)
            if masked:
                log_value = '<masked>'
            self.__logger.info('WLSDPLY-20012', key, log_value, class_name=self._class_name, method_name=_method_name)
        else:
            self.__wlst_helper.set_with_cmo(wlst_attr_name, wlst_attr_value, masked=masked)
        return

    #
    # internal lookup methods
    #

    def __build_target_mbean_list(self, target_value, wlst_value, location, include_jms=False):
        """
        Construct the target MBean list.
        :param target_value: the target value
        :param wlst_value: the existing value from WLST
        :param include_jms: whether or not to include JMS targets, the default is False
        :return: the Java array of MBeans ObjectNames
        :raises BundleAwareException of the specified type: if an error occurs
        """
        target_names = TypeUtils.convertToType(List, target_value)  # type: list of str
        target_names = self.__merge_existing_items(target_names, wlst_value)

        name_list = []
        for target_name in target_names:
            target_mbean = self.__find_target(target_name, location, include_jms=include_jms)
            name_list.append(target_mbean.getObjectName())

        return jarray.array(name_list, ObjectName)

    def __build_server_mbean_list(self, value, wlst_value):
        """
        Construct the server MBean list.
        :param value: the value
        :param wlst_value: the existing value from WLST
        :return: the Java array of MBeans ObjectNames
        :raises BundleAwareException of the specified type: if an error occurs
        """
        server_names = TypeUtils.convertToType(List, value)  # type: list of str
        server_names = self.__merge_existing_items(server_names, wlst_value)

        name_list = []
        for server_name in server_names:
            mbean = self.__find_in_location(LocationContext(), SERVER, server_name, required=True)
            name_list.append(mbean.getObjectName())

        return jarray.array(name_list, ObjectName)

    def __build_virtual_target_mbean_list(self, target_value, wlst_value):
        """
        Construct the virtual target MBean list.
        :param target_value: the target value
        :param wlst_value: the existing value from WLST
        :return: for offline, a list of MBeans; for online, a jarray of MBean ObjectNames
        :raises BundleAwareException of the specified type: if an error occurs
        """
        target_names = TypeUtils.convertToType(List, target_value)  # type: list of str
        target_names = self.__merge_existing_items(target_names, wlst_value)

        if self.__wlst_mode == WlstModes.ONLINE:
            name_list = []
            for target_name in target_names:
                target_mbean = self.__find_in_location(LocationContext(), VIRTUAL_TARGET, target_name, required=True)
                name_list.append(target_mbean.getObjectName())
            return jarray.array(name_list, ObjectName)
        else:
            mbean_list = []
            for target_name in target_names:
                target_mbean = self.__find_in_location(LocationContext(), VIRTUAL_TARGET, target_name, required=True)
                mbean_list.append(target_mbean)
            return mbean_list

    def __find_target(self, target_name, location, include_jms=False):
        """
        Find a target by name.
        :param target_name: the target name
        :param include_jms: whether or not to include JMS in the search, the default is False
        :return: the MBean for the target
        :raises BundleAwareException of the specified type: if an error occurs
        """
        method_name = '__find_target'
        domain_location = self.__get_domain_location(location)
        for type_name in self.__target_type_names:
            mbean = self.__find_in_location(domain_location, type_name, target_name)
            if mbean is not None:
                return mbean

        if include_jms:
            mbean = self.__find_in_resource_group_or_domain(location, JMS_SERVER, target_name)
            if mbean is not None:
                return mbean

        ex = exception_helper.create_exception(self.__exception_type, 'WLSDPLY-19200', target_name)
        self.__logger.throwing(class_name=self._class_name, method_name=method_name, error=ex)
        raise ex

    def __find_jms_destination_mbean(self, location, destination_name):
        """
        Find the destination with the specified name and return its WLST mbean.
        :param location: the WLST location of the attribute
        :param destination_name: the name of the destination to find
        :return: the mbean for the destination
        :raises BundleAwareException of the specified type: if destination is not found
        """
        method_name = '__find_jms_destination_mbean'

        resource_location = self.__get_parent_location(location, JMS_RESOURCE)
        for type_name in self.__destination_type_names:
            mbean = self.__find_in_location(resource_location, type_name, destination_name)
            if mbean is not None:
                return mbean

        _resource_type, resource_name = self.__alias_helper.get_model_type_and_name(resource_location)
        ex = exception_helper.create_exception(self.__exception_type, 'WLSDPLY-19201', destination_name, resource_name)
        self.__logger.throwing(class_name=self._class_name, method_name=method_name, error=ex)
        raise ex

    def __find_persistent_store(self, location, store_name):
        """
        Find the persistent store with the specified name and return its WLST mbean.
        :param location: the WLST location of the attribute
        :param store_name: the name of the store to find
        :return: the mbean for the store
        :raises BundleAwareException of the specified type: if store is not found
        """
        method_name = '__find_persistent_store'
        for type_name in self.__persistent_store_type_names:
            mbean = self.__find_in_resource_group_or_domain(location, type_name, store_name)
            if mbean is not None:
                return mbean

        ex = exception_helper.create_exception(self.__exception_type, 'WLSDPLY-19202', PERSISTENT_STORE, store_name)
        self.__logger.throwing(class_name=self._class_name, method_name=method_name, error=ex)
        raise ex

    def __find_saf_destination_mbean(self, location, destination_name):
        """
        Find the SAF destination with the specified name and return its WLST mbean.
        :param location: the WLST location of the attribute
        :param destination_name: the name of the SAF destination to find
        :return: the mbean for the SAF destination
        :raises BundleAwareException of the specified type: if SAF destination is not found
        """
        method_name = '__find_saf_destination_mbean'
        resource_location = self.__get_parent_location(location, JMS_RESOURCE)
        destination_location = LocationContext(resource_location).append_location(SAF_IMPORTED_DESTINATION)
        existing_sets = self.__get_existing_object_list(destination_location)

        token_name = self.__alias_helper.get_name_token(destination_location)
        for set_name in existing_sets:
            if token_name is not None:
                destination_location.add_name_token(token_name, set_name)
            for type_name in self.__saf_destination_type_names:
                mbean = self.__find_in_location(destination_location, type_name, destination_name)
                if mbean is not None:
                    return mbean

        _resource_type, resource_name = self.__alias_helper.get_model_type_and_name(resource_location)
        ex = exception_helper.create_exception(self.__exception_type, 'WLSDPLY-19203', destination_name, resource_name)
        self.__logger.throwing(class_name=self._class_name, method_name=method_name, error=ex)
        raise ex

    def __find_wldf_action(self, watch_location, action_name):
        """
        Find the WLDF action with the specified name and return its WLST mbean.
        :param watch_location: the WLST location of the watch notification
        :param action_name: the name of the action to find
        :return: the mbean for the action
        :raises BundleAwareException of the specified type: if action is not found
        """
        method_name = '__find_wldf_action'
        for type_name in self.__watch_action_types:
            mbean = self.__find_in_location(watch_location, type_name, action_name)
            if mbean is not None:
                return mbean

        ex = exception_helper.create_exception(self.__exception_type, 'WLSDPLY-19202', WATCH_NOTIFICATION, action_name)
        self.__logger.throwing(class_name=self._class_name, method_name=method_name, error=ex)
        raise ex

    def __find_in_resource_group_or_domain(self, location, element_type, name, required=False):
        """
        Find the element with the specified name and type and return its WLST mbean.
        If the specified location is in a resource group, search only that resource group.
        :param location: the WLST location of the attribute
        :param name: the name of the element to find
        :param required: indicates exception should be thrown if not found
        :return: the mbean for the destination
        :raises BundleAwareException of the specified type: if element is not found, and required is True
        """
        method_name = '__find_in_resource_group_or_domain'

        in_resource_group = RESOURCE_GROUP in location.get_model_folders()
        if in_resource_group:
            resource_group_location = self.__get_parent_location(location, RESOURCE_GROUP)
            mbean = self.__find_in_location(resource_group_location, element_type, name)
            if mbean is None:
                template_id = self.__wlst_helper.get("ResourceGroupTemplate")
                domain_location = self.__get_domain_location(location)
                mbean = self.__find_in_location(domain_location, RESOURCE_GROUP_TEMPLATE, template_id)
        else:
            location = self.__get_domain_location(location)
            mbean = self.__find_in_location(location, element_type, name)

        if required and (mbean is None):
            ex = exception_helper.create_exception(self.__exception_type, 'WLSDPLY-19202', element_type, name)
            self.__logger.throwing(class_name=self._class_name, method_name=method_name, error=ex)
            raise ex
        return mbean

    def __find_in_location(self, location, element_type, name, required=False):
        """
        Find the element with the specified name and type and return its WLST mbean.
        :param location: the WLST location of the attribute
        :param element_type: the thype of the element to be found
        :param name: the name of the element to find
        :param required: indicates exception should be thrown if not found
        :return: the mbean for the destination
        :raises BundleAwareException of the specified type: if element is not found, and required is True
        """
        method_name = '__find_in_location'

        location = LocationContext(location).append_location(element_type)
        if self.__alias_helper.get_wlst_mbean_type(location) is not None:
            existing_names = self.__get_existing_object_list(location)
            if name in existing_names:
                location_type, location_name = self.__alias_helper.get_model_type_and_name(location)
                self.__logger.fine('WLSDPLY-19204', element_type, name, location_type, location_name,
                                   class_name=self._class_name, method_name=method_name)
                token = self.__alias_helper.get_name_token(location)
                location.add_name_token(token, name)
                path = self.__alias_helper.get_wlst_attributes_path(location)
                return self.__wlst_helper.get_mbean_for_wlst_path(path)

        if required:
            ex = exception_helper.create_exception(self.__exception_type, 'WLSDPLY-19202', element_type, name)
            self.__logger.throwing(class_name=self._class_name, method_name=method_name, error=ex)
            raise ex

        return None

    def __get_domain_location(self, location):
        """
        Returns a copy of the specified location with all folders removed, but tokens intact.
        :param location: the location to be examined
        :return: the domain location
        """
        _method_name = '__get_domain_location'
        self.__logger.entering(str(location), class_name=self._class_name, method_name=_method_name)

        location = LocationContext(location)
        while len(location.get_model_folders()) > 0:
            location.pop_location()
        return location

    def __get_parent_location(self, location, folder_name):
        """
        Searches the specified location for the specified folder name, and returns the corresponding location.
        :param location: the location to be examined
        :param folder_name: the folder name to find
        :return: the parent location
        :raises BundleAwareException of the specified type: if the folder is not found in the location folders list
        """
        method_name = '__get_parent_location'

        try:
            location = LocationContext(location)
            resource_index = location.get_model_folders().index(folder_name)
            while len(location.get_model_folders()) > resource_index + 1:
                location.pop_location()
        except:
            # index throws a ValueError if the item is not in the list...
            ex = exception_helper.create_exception(self.__exception_type, 'WLSDPLY-19205', folder_name, str(location))
            self.__logger.throwing(class_name=self._class_name, method_name=method_name, error=ex)
            raise ex
        return location

    def __get_existing_object_list(self, location):
        """
        Convenience method to get the existing object list by location's list path
        :param location: the location
        :return: the list of existing names
        :raises BundleAwareException of the specified type: if an error occurs
        """
        _method_name = '__get_existing_object_list'

        self.__logger.entering(str(location), class_name=self._class_name, method_name=_method_name)
        list_path = self.__alias_helper.get_wlst_list_path(location)
        existing_names = self.__wlst_helper.get_existing_object_list(list_path)
        self.__logger.exiting(class_name=self._class_name, method_name=_method_name, result=existing_names)
        return existing_names

    #
    # methods for merging existing values
    #

    def __merge_existing_items(self, items, existing_value):
        """
        Merge the specified items with the items represented by existing value, and return the result.
        :param items: the attribute name
        :param existing_value: the value representing the existing items (may be a string or list)
        :return: the merged list of items
        :raises BundleAwareException of the specified type: if the WLDF Action/Notification is not found
        """
        _method_name = '__merge_existing_items'
        self.__logger.entering(str(items), str(existing_value), class_name=self._class_name, method_name=_method_name)

        existing_items = TypeUtils.convertToType(List, existing_value)  # type: list of str
        no_existing_items = (existing_items is None) or (len(existing_items) == 0)
        if no_existing_items:
            result = items
        else:
            result = list(Set(items).union(Set(existing_items)))
        return result