from collections import defaultdict
from xml.etree import ElementTree
import ast
import ntpath
import os

from parallels.core import MigrationError
from parallels.core.logging import get_logger
from parallels.core.reports.model.issue import Issue
from parallels.core.runners.base import BaseRunner
from parallels.core.utils import migrator_utils
from parallels.core.utils.base64_utils import base64_decode
from parallels.core.utils.common import cached, safe_format, is_unicode_or_utf8_string
from parallels.core.utils.database_utils import db_type_pretty_name
from parallels.core.utils.migrator_utils import safe_mail_idn_decode
from parallels.plesk.source.plesk import messages
from parallels.plesk.source.plesk.hosting_description.plesk_entity.account import Account
from parallels.plesk.source.plesk.hosting_description.plesk_entity.anonymous_ftp import AnonymousFtp
from parallels.plesk.source.plesk.hosting_description.plesk_entity.aps_filter import ApsFilter
from parallels.plesk.source.plesk.hosting_description.plesk_entity.aps_filter_item import ApsFilterItem
from parallels.plesk.source.plesk.hosting_description.plesk_entity.aps_resource import ApsResource
from parallels.plesk.source.plesk.hosting_description.plesk_entity.certificate import Certificate
from parallels.plesk.source.plesk.hosting_description.plesk_entity.client import Client
from parallels.plesk.source.plesk.hosting_description.plesk_entity.custom_button import CustomButton
from parallels.plesk.source.plesk.hosting_description.plesk_entity.database import Database
from parallels.plesk.source.plesk.hosting_description.plesk_entity.database_server import DatabaseServer
from parallels.plesk.source.plesk.hosting_description.plesk_entity.database_user import DatabaseUser
from parallels.plesk.source.plesk.hosting_description.plesk_entity.dns_record import DnsRecord
from parallels.plesk.source.plesk.hosting_description.plesk_entity.dns_zone import DnsZone
from parallels.plesk.source.plesk.hosting_description.plesk_entity.domain import Domain
from parallels.plesk.source.plesk.hosting_description.plesk_entity.domain_alias import DomainAlias
from parallels.plesk.source.plesk.hosting_description.plesk_entity.domain_service import DomainService
from parallels.plesk.source.plesk.hosting_description.plesk_entity.dynamic_ip_security import DynamicIpSecurity
from parallels.plesk.source.plesk.hosting_description.plesk_entity.forwarding import Forwarding
from parallels.plesk.source.plesk.hosting_description.plesk_entity.ftp_user import FtpUser
from parallels.plesk.source.plesk.hosting_description.plesk_entity.general_permission import GeneralPermission
from parallels.plesk.source.plesk.hosting_description.plesk_entity.hosting import Hosting
from parallels.plesk.source.plesk.hosting_description.plesk_entity.hotlink_protection import HotlinkProtection
from parallels.plesk.source.plesk.hosting_description.plesk_entity.iis_app_pool import IisAppPool
from parallels.plesk.source.plesk.hosting_description.plesk_entity.ip_address import IpAddress
from parallels.plesk.source.plesk.hosting_description.plesk_entity.ip_pool_item import IpPoolItem
from parallels.plesk.source.plesk.hosting_description.plesk_entity.log_rotation import LogRotation
from parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_list import MailList
from parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_name import MailName
from parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_responder import MailResponder
from parallels.plesk.source.plesk.hosting_description.plesk_entity.note import Note
from parallels.plesk.source.plesk.hosting_description.plesk_entity.php_handler import PhpHandler
from parallels.plesk.source.plesk.hosting_description.plesk_entity.php_settings import PhpSettings
from parallels.plesk.source.plesk.hosting_description.plesk_entity.protected_directory import ProtectedDirectory
from parallels.plesk.source.plesk.hosting_description.plesk_entity.protected_directory_user import ProtectedDirectoryUser
from parallels.plesk.source.plesk.hosting_description.plesk_entity.role import Role
from parallels.plesk.source.plesk.hosting_description.plesk_entity.role_general_permission import RoleGeneralPermission
from parallels.plesk.source.plesk.hosting_description.plesk_entity.role_service_permission import RoleServicePermission
from parallels.plesk.source.plesk.hosting_description.plesk_entity.sa_mail_name import SpamAssassinMailName
from parallels.plesk.source.plesk.hosting_description.plesk_entity.service_instance import ServiceInstance
from parallels.plesk.source.plesk.hosting_description.plesk_entity.service_permission import ServicePermission
from parallels.plesk.source.plesk.hosting_description.plesk_entity.service_provider import ServiceProvider
from parallels.plesk.source.plesk.hosting_description.plesk_entity.spam_filter import SpamFilter
from parallels.plesk.source.plesk.hosting_description.plesk_entity.sys_user import SysUser
from parallels.plesk.source.plesk.hosting_description.plesk_entity.subscription import Subscription
from parallels.plesk.source.plesk.hosting_description.plesk_entity.subscription_plan import SubscriptionPlan
from parallels.plesk.source.plesk.hosting_description.plesk_entity.template import Template
from parallels.plesk.source.plesk.hosting_description.plesk_entity.user import User
from parallels.plesk.source.plesk.hosting_description.plesk_entity.web_user import WebUser
from parallels.plesk.source.plesk.hosting_description.plesk_decryptor import PleskDecryptor, \
    EncryptedPasswordItem
from parallels.plesk.source.plesk.hosting_description.utils import data_dump
import parallels.plesk.source.plesk

logger = get_logger(__name__)


class PleskEntityFactory(object):
    """
    Use the decorator data_dump() to catch unhandled exceptions
    """

    MAX_ENTITIES_PER_QUERY = 100

    def __init__(self, selection, dump_agent):
        """
        :type selection: parallels.core.utils.pmm.agent.DumpAll|parallels.core.utils.pmm.agent.DumpSelected
        :type dump_agent: parallels.core.utils.pmm.agent.PmmMigrationAgentBase
        """
        self._selection = selection
        self._dump_agent = dump_agent

    @cached
    def get_admin(self):
        """
        :rtype: parallels.plesk.source.plesk.hosting_description.plesk_entity.client.Client
        """
        admin_id = self._get_admin_id()
        admin = self.get_selected_clients().get(admin_id)
        if admin:
            return admin
        clients = self._get_clients_by_id([admin_id])
        for admin in clients.values():
            return admin
        raise MigrationError(messages.UNABLE_TO_FIND_ADMIN_USER)

    @cached
    def get_templates(self, owner_id=None, template_type=None):
        """
        :type owner_id: str|unicode
        :type template_type: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.template.Template]
        """
        return [
            template for template in self.get_selected_templates().values()
            if (not owner_id or template.owner_id == owner_id) and
            (not template_type or template.template_type == template_type)
        ]

    @cached
    def get_subscriptions(self, object_id=None, object_type=None):
        """
        :type object_id: str|unicode
        :type object_type: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.subscription.Subscription]
        """
        return [
            subscription for subscription in self.get_selected_subscriptions().values()
            if (not object_id or subscription.object_id == object_id) and
            (not object_type or subscription.object_type == object_type)
        ]

    @cached
    def get_subscription_properties(self, object_id, object_type):
        """
        :type object_id: str|unicode
        :type object_type: str|unicode
        :rtype: dict[str|unicode, str|unicode]
        """
        for subscription in self.get_subscriptions(object_id=object_id, object_type=object_type):
            return self.get_selected_subscriptions_properties().get(subscription.subscription_id, {})
        return {}

    @cached
    def get_subscriptions_plans(self, subscription_id=None):
        """
        :type subscription_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.subscription_plan.SubscriptionPlan]
        """
        return [
            subscription_plan for subscription_plan in self.get_selected_subscriptions_plans()
            if (not subscription_id or subscription_plan.subscription_id == subscription_id)
        ]

    @cached
    def get_clients(self, owner_id=None, client_type=None):
        """
        :type owner_id: str|unicode
        :type client_type: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.client.Client]
        """
        return [
            client for client in self.get_selected_clients().values()
            if (not owner_id or client.owner_id == owner_id) and
            (not client_type or client.client_type == client_type)
        ]

    @cached
    def get_users(self, owner_id=None, is_built_in=None):
        """
        :type owner_id: str|unicode
        :type is_built_in: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.user.User]
        """
        return [
            user for user in self.get_selected_users().values()
            if (not owner_id or user.owner_id == owner_id) and
            (not is_built_in or user.is_built_in == is_built_in)
        ]

    @cached
    def get_domains(self, owner_id=None, webspace_id=None, parent_domain_id=None):
        """
        :type owner_id: str|unicode
        :type webspace_id: str|unicode
        :type parent_domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain]
        """
        return [
            domain for domain in self.get_selected_domains().values()
            if (not owner_id or domain.owner_id == owner_id) and
            (not webspace_id or domain.webspace_id == webspace_id) and
            (not parent_domain_id or domain.parent_domain_id == parent_domain_id)
        ]

    @cached
    def get_mail_names(self, user_id=None, domain_id=None):
        """
        :type user_id: str|unicode
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_name.MailName]
        """
        return [
            mail_name for mail_name in self.get_selected_mail_names().values()
            if (not user_id or mail_name.user_id == user_id) and
            (not domain_id or mail_name.domain_id == domain_id)
        ]

    @cached
    def get_mail_responders(self, mail_name_id=None):
        """
        :type mail_name_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_responder.MailResponder]
        """
        return [
            mail_responder for mail_responder in self.get_selected_mail_responders().values()
            if (not mail_name_id or mail_responder.mail_name_id == mail_name_id)
        ]

    @cached
    def get_mail_lists(self, domain_id=None, list_name=None):
        """
        :type domain_id: str|unicode
        :type list_name: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_list.MailList]
        """
        return [
            mail_list for mail_list in self.get_selected_mail_lists().values()
            if (not domain_id or mail_list.domain_id == domain_id) and
            (not list_name or mail_list.name == list_name)
        ]

    @cached
    def get_custom_buttons(self, level_id=None, level=None):
        """
        :type level_id: str|unicode
        :type level: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.custom_button.CustomButton]
        """
        return [
            custom_button for custom_button in self.get_selected_custom_buttons().values()
            if (not level_id or custom_button.level_id == level_id) and
            (not level or custom_button.level == level)
        ]

    @cached
    def get_iis_app_pools(self, owner_id=None, owner_type=None):
        """
        :type owner_id: str|unicode
        :type owner_type: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.iis_app_pool.IisAppPool]
        """
        return [
            iis_app_pool for iis_app_pool in self.get_selected_iis_app_pools().values()
            if (not owner_id or iis_app_pool.owner_id == owner_id) and
            (not owner_type or iis_app_pool.owner_type == owner_type)
        ]

    @cached
    def get_domain_aliases(self, domain_id=None):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.domain_alias.DomainAlias]
        """
        return [
            domain_alias for domain_alias in self.get_selected_domain_aliases().values()
            if (not domain_id or domain_alias.domain_id == domain_id)
        ]

    @cached
    def get_dns_records(self, dns_zone_id=None):
        """
        :type dns_zone_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.dns_record.DnsRecord]
        """
        return [
            dns_record for dns_record in self.get_selected_dns_records().values()
            if (not dns_zone_id or dns_record.dns_zone_id == dns_zone_id)
        ]

    @cached
    def get_databases(self, domain_id=None):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.database.Database]
        """
        return [
            database for database in self.get_selected_databases().values()
            if (not domain_id or database.domain_id == domain_id)
        ]

    @cached
    def get_database_users(self, database_id=None, domain_id=None):
        """
        :type database_id: str|unicode
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.database_user.DatabaseUser]
        """
        return [
            database_user for database_user in self.get_selected_database_users().values()
            if (not database_id or database_user.database_id == database_id) and
            (not domain_id or database_user.domain_id == domain_id)
        ]

    @cached
    def get_ftp_users(self, domain_id=None):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.ftp_user.FtpUser]
        """
        return [
            ftp_user for ftp_user in self.get_selected_ftp_users().values()
            if (not domain_id or ftp_user.domain_id == domain_id)
        ]

    @cached
    def get_web_users(self, domain_id=None):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.web_user.WebUser]
        """
        return [
            web_user for web_user in self.get_selected_web_users().values()
            if (not domain_id or web_user.domain_id == domain_id)
        ]

    @cached
    def get_scheduled_tasks(self, sys_user_id=None):
        """
        :type sys_user_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.scheduled_task.ScheduledTask]
        """
        return [
            scheduled_task for scheduled_task in self.get_selected_scheduled_tasks().values()
            if (not sys_user_id or scheduled_task.sys_user_id == sys_user_id)
        ]

    @cached
    def get_aps_resources(self, plesk_id=None, plesk_type=None):
        """
        :type plesk_id: str|unicode
        :type plesk_type: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.aps_resource.ApsResource]
        """
        return [
            aps_resource for aps_resource in self.get_selected_aps_resources().values()
            if (not plesk_id or aps_resource.plesk_id == plesk_id) and
            (not plesk_type or aps_resource.plesk_type == plesk_type)
        ]

    @cached
    def get_protected_directories(self, domain_id=None):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.protected_directory.ProtectedDirectory]
        """
        return [
            protected_directory for protected_directory in self.get_selected_protected_directories().values()
            if (not domain_id or protected_directory.domain_id == domain_id)
        ]

    @cached
    def get_protected_directories_users(self, protected_directory_id=None):
        """
        :type protected_directory_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.protected_directory_user.ProtectedDirectoryUser]
        """
        return [
            protected_directory_user for protected_directory_user in self.get_selected_protected_directories_users().values()
            if (not protected_directory_id or protected_directory_user.protected_directory_id == protected_directory_id)
        ]

    @cached
    def get_domain_service(self, domain_id, service_type):
        """
        :type domain_id: str|unicode
        :type service_type: str|unicode
        :rtype: parallels.plesk.source.plesk.hosting_description.plesk_entity.domain_service.DomainService | None
        """
        for domain_service in self.get_selected_domain_services().values():
            if domain_service.domain_id == domain_id and domain_service.service_type == service_type:
                return domain_service
        return None

    def get_parameter(self, parameters_id, name):
        """
        :type parameters_id: str|unicode
        :type name: str|unicode
        :rtype: str|unicode|None
        """
        parameters = self.get_selected_parameters().get(parameters_id)
        if parameters:
            return parameters.get(name)
        return None

    def get_domain_parameter(self, domain_id, name):
        """
        :type domain_id: str|unicode
        :type name: str|unicode
        :rtype: str|unicode|None
        """
        parameters = self.get_selected_domain_parameters().get(domain_id)
        if parameters:
            return parameters.get(name)
        return None

    def get_mail_name_parameter(self, mail_name_id, name):
        """
        :type mail_name_id: str|unicode
        :type name: str|unicode
        :rtype: str|unicode|None
        """
        parameters = self.get_selected_mail_names_parameters().get(mail_name_id)
        if parameters:
            return parameters.get(name)
        return None

    @cached
    @data_dump(messages.DATA_DUMP_SERVER_SETTINGS, {})
    def get_misc_parameters(self):
        """
        :rtype: dict[str|unicode, str|unicode]
        """
        rows = self._dump_agent.execute_sql("SELECT `param`, `val` FROM `misc`")
        return {
            row['param']: row['val']
            for row in rows
        }

    def get_mail_list_settings(self, domain_id, list_name):
        """
        :type domain_id: str|unicode
        :type list_name: str|unicode
        :rtype: dict|None
        """
        domain_mail_lists_settings = self.get_selected_mail_lists_settings().get(domain_id)
        if domain_mail_lists_settings:
            return domain_mail_lists_settings.get(list_name)
        return None

    def get_spam_filter_preference_values(self, spam_filter_id, name):
        """
        :type spam_filter_id: str|unicode
        :type name: str|unicode
        :rtype: list[str|unicode] | None
        """
        spam_filter_preferences = self.get_selected_spam_filters_preferences().get(spam_filter_id)
        if spam_filter_preferences:
            return spam_filter_preferences.get(name)
        return None

    def get_spam_filter_preference_value(self, spam_filter_id, name):
        """
        :type spam_filter_id: str|unicode
        :type name: str|unicode
        :rtype: str|unicode|None
        """
        spam_filter_preference_values = self.get_spam_filter_preference_values(spam_filter_id, name)
        if spam_filter_preference_values:
            return spam_filter_preference_values[0]
        return None

    def get_sa_parameter_values(self, sa_mail_name_id, name):
        """
        :type sa_mail_name_id: str|unicode
        :type name: str|unicode
        :rtype: list[str|unicode] | None
        """
        sa_parameters = self.get_selected_sa_parameters().get(sa_mail_name_id)
        if sa_parameters:
            return sa_parameters.get(name)
        return None

    def get_sa_parameter_value(self, sa_mail_name_id, name):
        """
        :type sa_mail_name_id: str|unicode
        :type name: str|unicode
        :rtype: list[str|unicode] | None
        """
        sa_parameter_values = self.get_sa_parameter_values(sa_mail_name_id, name)
        if sa_parameter_values:
            return sa_parameter_values[0]
        return None

    @cached
    @data_dump(messages.DATA_DUMP_SERVER_SETTINGS)
    def get_management_service_node_id(self):
        """
        :rtype: str|unicode|None
        """
        rows = self._dump_agent.execute_sql("SELECT `id` FROM `ServiceNodes` WHERE `ipAddress` = 'local'")
        if rows:
            return rows[0]['id']
        return None

    @cached
    @data_dump(messages.DATA_DUMP_PHP_HANDLERS, {})
    def get_php_handlers(self, service_node_id):
        """
        :type service_node_id: str|unicode
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.php_handler.PhpHandler]
        """
        php_handlers = {}
        for name, value in self.get_service_node_environment(service_node_id, 'phphandlers').items():
            if name == 'synced':
                continue
            try:
                php_handler_node = ElementTree.fromstring(value)
                php_handler_id = php_handler_node.findtext('id')
                if php_handler_id is None:
                    continue
                php_handlers[php_handler_id] = PhpHandler(
                    handler_id=php_handler_id,
                    handler_type=php_handler_node.findtext('type'),
                    handler_type_name=php_handler_node.findtext('typeName'),
                    version=php_handler_node.findtext('version'),
                    display_name=php_handler_node.findtext('displayname'),
                    registered=php_handler_node.findtext('registered'),
                    enabled='true',
                )
            except Exception as e:
                logger.exception()
                logger.warning(safe_format(messages.UNABLE_TO_GET_PHP_HANDLER_PROPERTIES, description=value, error=e))

        for name, value in self.get_service_node_configuration(service_node_id, 'phphandlers').items():
            try:
                php_handler_node = ElementTree.fromstring(value)
                php_handler_id = name
                php_handler = php_handlers.get(php_handler_id)
                if php_handler is None:
                    continue
                php_handlers[php_handler_id] = PhpHandler(
                    handler_id=php_handler.handler_id,
                    handler_type=php_handler.handler_type,
                    handler_type_name=php_handler.handler_type_name,
                    version=php_handler.version,
                    display_name=php_handler.display_name,
                    registered=php_handler.registered,
                    enabled=php_handler_node.findtext('enabled'),
                )
            except Exception as e:
                logger.exception()
                logger.warning(safe_format(messages.UNABLE_TO_GET_PHP_HANDLER_PROPERTIES, description=value, error=e))
        return php_handlers

    @cached
    @data_dump(messages.DATA_DUMP_SERVER_COMPONENTS, {})
    def get_server_components(self, service_node_id):
        """
        :type service_node_id: str|unicode
        :rtype: dict[str|unicode, str|unicode|None]
        """
        server_components = {}
        if self._dump_agent.source_server().is_windows():
            packages_node = self._get_all_packages()
            for package_node in packages_node.findall('type/package'):
                if package_node.get('state') != 'not_installed':
                    server_components[package_node.get('name')] = package_node.get('version')
        else:
            for name, value in self.get_service_node_environment(service_node_id, 'componentsPackages').items():
                if name == 'synced':
                    continue
                server_components[name] = value
        return server_components

    @cached
    @data_dump(messages.DATA_DUMP_SERVER_SETTINGS, {})
    def get_service_node_environment(self, service_node_id, section_name):
        """
        :type service_node_id: str|unicode
        :type section_name: str|unicode
        :rtype: dict[str|unicode, str|unicode]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `name`, `value`
            FROM `ServiceNodeEnvironment`
            WHERE `serviceNodeId` = {service_node_id} AND `section` = '{section_name}'
        """.format(service_node_id=service_node_id, section_name=section_name))
        return {
            row['name']: row['value']
            for row in rows
        }

    @cached
    @data_dump(messages.DATA_DUMP_SERVER_SETTINGS, {})
    def get_service_node_configuration(self, service_node_id, section_name):
        """
        :type service_node_id: str|unicode
        :type section_name: str|unicode
        :rtype: dict[str|unicode, str|unicode]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `name`, `value`
            FROM `ServiceNodeConfiguration`
            WHERE `serviceNodeId` = {service_node_id} AND `section` = '{section_name}'
        """.format(service_node_id=service_node_id, section_name=section_name))
        return {
            row['name']: row['value']
            for row in rows
        }

    @cached
    def get_applications(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[xml.etree.ElementTree.Element]
        """
        return [
            application
            for application in self.get_selected_applications()
            if application.findtext('vhost/id') == domain_id
        ]

    @cached
    def get_subscription_domains(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain]
        """
        domain = self.get_selected_domains().get(domain_id)
        if not domain:
            return []
        if domain.webspace_id == '0':
            main_domain = domain
        else:
            main_domain = self.get_selected_domains().get(domain.webspace_id)
        if not main_domain:
            return [domain]
        subscription_domains = [main_domain]
        for subscription_domain in self.get_domains(webspace_id=main_domain.domain_id):
            subscription_domains.append(subscription_domain)
        return subscription_domains

    @cached
    @data_dump(messages.DATA_DUMP_SERVICE_PLANS, {})
    def get_selected_templates(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.template.Template]
        """
        selected_templates_ids = set()
        for subscription_plan in self.get_selected_subscriptions_plans():
            if subscription_plan.quantity == '0':
                continue
            selected_templates_ids.add(subscription_plan.plan_id)
        templates = {}
        for templates_ids in self._iter_ranges(selected_templates_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `uuid`, `owner_id`, `type`, `name`
                FROM `Templates`
                WHERE `id` IN ({templates_ids})
            """.format(templates_ids=format_ids_list(templates_ids)))
            for row in rows:
                templates[row['id']] = Template(
                    template_id=row['id'], guid=row['uuid'], owner_id=row['owner_id'], template_type=row['type'],
                    name=row['name'],
                )
        return templates

    @cached
    @data_dump(messages.DATA_DUMP_SERVICE_PLANS, {})
    def get_selected_templates_items(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        items = defaultdict(dict)
        for templates_ids in self._iter_ranges(self.get_selected_templates().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `tmpl_id`, `element`, `value`
                FROM `TmplData`
                WHERE `tmpl_id` IN ({templates_ids})
            """.format(templates_ids=format_ids_list(templates_ids)))
            for row in rows:
                items[row['tmpl_id']][row['element']] = row['value']
        return items

    @cached
    @data_dump(messages.DATA_DUMP_IP_ADDRESSES, {})
    def get_selected_ip_pools_items(self):
        """
        :rtype: dict[str|unicode, list[parallels.plesk.source.plesk.hosting_description.plesk_entity.ip_pool_item.IpPoolItem]]
        """
        selected_ip_pools_ids = []
        for template_items in self.get_selected_templates_items().values():
            tmpl_pool_id = template_items.get('tmpl_pool_id')
            if tmpl_pool_id is not None and tmpl_pool_id.isdigit():
                selected_ip_pools_ids.append(tmpl_pool_id)
        for client in self.get_selected_clients().values():
            selected_ip_pools_ids.append(client.ip_pool_id)
        ip_pool_items = defaultdict(list)
        for ip_pools_ids in self._iter_ranges(selected_ip_pools_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `ip_address_id`, `type`
                FROM `ip_pool`
                WHERE `id` IN ({ip_pools_ids})
            """.format(ip_pools_ids=format_ids_list(ip_pools_ids)))
            for row in rows:
                ip_pool_items[row['id']].append(IpPoolItem(
                    ip_pool_id=row['id'], ip_address_id=row['ip_address_id'], ip_address_type=row['type'],
                ))
        return ip_pool_items

    @cached
    @data_dump(messages.DATA_DUMP_IP_ADDRESSES, {})
    def get_selected_ip_collections(self):
        """
        :rtype: dict[str|unicode, list[str|unicode]]
        """
        selected_ip_collections_ids = [
            domain_service.ip_collection_id for domain_service in self.get_selected_domain_services().values()
        ]
        ip_collections = defaultdict(list)
        for ip_collections_ids in self._iter_ranges(selected_ip_collections_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `ipCollectionId`, `ipAddressId`
                FROM `IpAddressesCollections`
                WHERE `ipCollectionId` IN ({ip_collections_ids})
            """.format(ip_collections_ids=format_ids_list(ip_collections_ids)))
            for row in rows:
                ip_collections[row['ipCollectionId']].append(row['ipAddressId'])
        return ip_collections

    @cached
    @data_dump(messages.DATA_DUMP_IP_ADDRESSES, {})
    def get_selected_ip_addresses(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.ip_address.IpAddress]
        """
        selected_ip_addresses_ids = set()
        for ip_pool_items in self.get_selected_ip_pools_items().values():
            for ip_pool_item in ip_pool_items:
                selected_ip_addresses_ids.add(ip_pool_item.ip_address_id)
        ip_addresses = {}
        for ip_addresses_ids in self._iter_ranges(selected_ip_addresses_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `ip_address`, `serviceNodeId`, `ftps`
                FROM `IP_Addresses`
                WHERE `id` IN ({ip_addresses_ids})
            """.format(ip_addresses_ids=format_ids_list(ip_addresses_ids)))
            for row in rows:
                ip_addresses[row['id']] = IpAddress(
                    ip_address_id=row['id'], ip_address=row['ip_address'], service_node_id=row['serviceNodeId'],
                    ftps=row['ftps'],
                )
        return ip_addresses

    @cached
    @data_dump(messages.DATA_DUMP_APPLICATIONS_FILTERS, {})
    def get_selected_aps_filters(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.aps_filter.ApsFilter]
        """
        selected_aps_filters_ids = set()
        for template_items in self.get_selected_templates_items().values():
            aps_bundle_filter_id = template_items.get('aps_bundle_filter_id')
            if aps_bundle_filter_id is not None and aps_bundle_filter_id.isdigit():
                selected_aps_filters_ids.add(aps_bundle_filter_id)
        for subscription_properties in self.get_selected_subscriptions_properties().values():
            aps_bundle_filter_id = subscription_properties.get('aps_bundle_filter_id')
            if aps_bundle_filter_id is not None and aps_bundle_filter_id.isdigit():
                selected_aps_filters_ids.add(aps_bundle_filter_id)
        aps_filters = {}
        for aps_filters_ids in self._iter_ranges(selected_aps_filters_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `type`
                FROM `smb_apsBundleFilters`
                WHERE `id` IN ({aps_filters_ids})
            """.format(aps_filters_ids=format_ids_list(aps_filters_ids)))
            for row in rows:
                aps_filters[row['id']] = ApsFilter(
                    filter_id=row['id'], filter_type=row['type'],
                )
        return aps_filters

    @cached
    @data_dump(messages.DATA_DUMP_APPLICATIONS_FILTERS, {})
    def get_selected_aps_filters_items(self):
        """
        :rtype: dict[str|unicode, list[parallels.plesk.source.plesk.hosting_description.plesk_entity.aps_filter_item.ApsFilterItem]]
        """
        selected_ip_pools_ids = []
        for template_items in self.get_selected_templates_items().values():
            tmpl_pool_id = template_items.get('tmpl_pool_id')
            if tmpl_pool_id is not None and tmpl_pool_id.isdigit():
                selected_ip_pools_ids.append(tmpl_pool_id)
        aps_filters_items = defaultdict(list)
        for aps_filters_items_ids in self._iter_ranges(self.get_selected_aps_filters().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `filterId`, `propertyName`, `propertyValue`
                FROM `smb_apsBundleFilterItems`
                WHERE `filterId` IN ({aps_filters_items_ids})
            """.format(aps_filters_items_ids=format_ids_list(aps_filters_items_ids)))
            for row in rows:
                aps_filters_items[row['filterId']].append(ApsFilterItem(
                    filter_id=row['filterId'], name=row['propertyName'], value=row['propertyValue'],
                ))
        return aps_filters_items

    @cached
    @data_dump(messages.DATA_DUMP_APPLICATIONS, {})
    def get_selected_aps_resources(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.aps_resource.ApsResource]
        """
        aps_resources = {}
        for databases_ids in self._iter_ranges(self.get_selected_databases().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `pleskId`, `pleskType`
                FROM `apsResources`
                WHERE `pleskType` = 'db' AND `pleskId` IN ({databases_ids})
            """.format(databases_ids=format_ids_list(databases_ids)))
            for row in rows:
                aps_resources[row['id']] = ApsResource(
                    aps_resource_id=row['id'], plesk_id=row['pleskId'], plesk_type=row['pleskType'],
                )
        return aps_resources

    @cached
    @data_dump(messages.DATA_DUMP_LOG_ROTATION_SETTINGS, {})
    def get_selected_log_rotations(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.log_rotation.LogRotation]
        """
        selected_log_rotations_ids = []
        for template_items in self.get_selected_templates_items().values():
            logrotation_id = template_items.get('logrotation_id')
            if logrotation_id is not None and logrotation_id.isdigit():
                selected_log_rotations_ids.append(logrotation_id)
        for domain_id in self._get_selected_domain_ids():
            log_rotation_id = self.get_domain_parameter(domain_id, 'logrotation_id')
            if log_rotation_id is not None and log_rotation_id.isdigit():
                selected_log_rotations_ids.append(log_rotation_id)
        log_rotations = {}
        for log_rotations_ids in self._iter_ranges(selected_log_rotations_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `period_type`, `period`, `max_number_of_logfiles`, `compress_enable`, `email`, `turned_on`
                FROM `log_rotation`
                WHERE `id` IN ({log_rotations_ids})
            """.format(log_rotations_ids=format_ids_list(log_rotations_ids)))
            for row in rows:
                log_rotations[row['id']] = LogRotation(
                    log_rotation_id=row['id'], period_type=row['period_type'], period=row['period'],
                    max_number_of_log_files=row['max_number_of_logfiles'], compress_enable=row['compress_enable'],
                    email=row['email'], turned_on=row['turned_on'],
                )
        return log_rotations

    @cached
    @data_dump(messages.DATA_DUMP_PHP_SETTINGS, {})
    def get_selected_php_settings(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.php_settings.PhpSettings]
        """
        selected_php_settings_ids = []
        for template_items in self.get_selected_templates_items().values():
            php_settings_id = template_items.get('phpSettingsId')
            if php_settings_id is not None and php_settings_id.isdigit():
                selected_php_settings_ids.append(php_settings_id)
        for domain_id in self._get_selected_domain_ids():
            php_settings_id = self.get_domain_parameter(domain_id, 'phpSettingsId')
            if php_settings_id is not None and php_settings_id.isdigit():
                selected_php_settings_ids.append(php_settings_id)
        php_settings = {}
        for php_settings_ids in self._iter_ranges(selected_php_settings_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `noteId`
                FROM `PhpSettings`
                WHERE `id` IN ({php_settings_ids})
            """.format(php_settings_ids=format_ids_list(php_settings_ids)))
            for row in rows:
                php_settings[row['id']] = PhpSettings(php_settings_id=row['id'], note_id=row['noteId'])
        return php_settings

    @cached
    @data_dump(messages.DATA_DUMP_PHP_SETTINGS, {})
    def get_selected_php_settings_parameters(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        php_settings_parameters = defaultdict(dict)
        for php_settings_ids in self._iter_ranges(self.get_selected_php_settings().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `name`, `value`
                FROM `PhpSettingsParameters`
                WHERE `id` IN ({php_settings_ids})
            """.format(php_settings_ids=format_ids_list(php_settings_ids)))
            for row in rows:
                php_settings_parameters[row['id']][row['name']] = row['value']
        return php_settings_parameters

    @cached
    @data_dump(messages.DATA_DUMP_PHP_SETTINGS, {})
    def get_selected_notes(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.note.Note]
        """
        selected_notes_ids = []
        for php_settings in self.get_selected_php_settings().values():
            if php_settings.note_id != '0':
                selected_notes_ids.append(php_settings.note_id)
        notes = {}
        for notes_ids in self._iter_ranges(selected_notes_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `text`
                FROM `Notes`
                WHERE `id` IN ({notes_ids})
            """.format(notes_ids=format_ids_list(notes_ids)))
            for row in rows:
                notes[row['id']] = Note(note_id=row['id'], text=row['text'])
        return notes

    @cached
    @data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_PROPERTIES, {})
    def get_selected_subscriptions(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.subscription.Subscription]
        """
        subscriptions = {}

        def get_objects_subscriptions(object_type, objects_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `object_id`, `object_type`, `locked`, `synchronized`, `custom`
                FROM `Subscriptions`
                WHERE `object_type` = '{object_type}' AND `object_id` IN ({objects_ids})
            """.format(object_type=object_type, objects_ids=format_ids_list(objects_ids)))
            return {
                row['id']: Subscription(
                    subscription_id=row['id'], object_id=row['object_id'], object_type=row['object_type'],
                    locked=row['locked'], synchronized=row['synchronized'], custom=row['custom']
                )
                for row in rows
            }

        selected_clients_ids = [client.client_id for client in self.get_clients(client_type='reseller')]
        for clients_ids in self._iter_ranges(selected_clients_ids):
            subscriptions.update(get_objects_subscriptions('client', clients_ids))
        selected_domains_ids = [domain.domain_id for domain in self.get_domains(webspace_id='0')]
        for domains_ids in self._iter_ranges(selected_domains_ids):
            subscriptions.update(get_objects_subscriptions('domain', domains_ids))

        return subscriptions

    @cached
    @data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_PROPERTIES, {})
    def get_selected_subscriptions_properties(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        subscriptions_properties = defaultdict(dict)
        for subscriptions_ids in self._iter_ranges(self.get_selected_subscriptions().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `subscription_id`, `name`, `value`
                FROM `SubscriptionProperties`
                WHERE `subscription_id` IN ({subscriptions_ids})
            """.format(subscriptions_ids=format_ids_list(subscriptions_ids)))
            for row in rows:
                subscriptions_properties[row['subscription_id']][row['name']] = row['value']

        return subscriptions_properties

    @cached
    @data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_PROPERTIES, [])
    def get_selected_subscriptions_plans(self):
        """
        :rtype: list[parallels.plesk.source.plesk.hosting_description.plesk_entity.subscription_plan.SubscriptionPlan]
        """
        subscriptions_plans = []
        selected_subscriptions_ids = [
            subscription.subscription_id
            for subscription in self.get_selected_subscriptions().values() if subscription.custom != 'true'
        ]
        for subscriptions_ids in self._iter_ranges(selected_subscriptions_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `subscription_id`, `plan_id`, `quantity`
                FROM `PlansSubscriptions`
                WHERE `subscription_id` IN ({subscriptions_ids})
            """.format(subscriptions_ids=format_ids_list(subscriptions_ids)))
            for row in rows:
                subscriptions_plans.append(SubscriptionPlan(
                    subscription_id=row['subscription_id'], plan_id=row['plan_id'], quantity=row['quantity'],
                ))

        return subscriptions_plans

    @cached
    @data_dump(messages.DATA_DUMP_CLIENTS, {})
    def get_selected_clients(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.client.Client]
        """
        clients = {}
        for clients_ids in self._iter_ranges(self._get_selected_clients_ids()):
            clients.update(self._get_clients_by_id(clients_ids))
        return clients

    @cached
    @data_dump(messages.DATA_DUMP_PASSWORDS, {})
    def get_selected_accounts(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.account.Account]
        """
        selected_accounts_ids = []
        object_descriptions = {}

        selected_accounts_ids += [
            client.account_id
            for client in self.get_selected_clients().values()
        ]
        object_descriptions.update({
            client.account_id: safe_format(
                messages.OBJECT_DESCRIPTION_CLIENT, login=client.login, account_id=client.account_id
            )
            for client in self.get_selected_clients().values()
        })

        selected_accounts_ids += [
            sys_user.account_id
            for sys_user in self.get_selected_sys_users().values()
        ]
        object_descriptions.update({
           sys_user.account_id: safe_format(
               messages.OBJECT_DESCRIPTION_SYSTEM_USER, login=sys_user.login, account_id=sys_user.account_id
           )
           for sys_user in self.get_selected_sys_users().values()
        })

        selected_accounts_ids += [
            mail_name.account_id
            for mail_name in self.get_selected_mail_names().values()
        ]
        object_descriptions.update({
           mail_name.account_id: safe_format(
               messages.OBJECT_DESCRIPTION_MAIL_NAME,
               name=mail_name.name, domain=mail_name.domain_id, account_id=mail_name.account_id
           )
           for mail_name in self.get_selected_mail_names().values()
        })

        selected_accounts_ids += [
            database_user.account_id
            for database_user in self.get_selected_database_users().values()
        ]
        object_descriptions.update({
           database_user.account_id: safe_format(
               messages.OBJECT_DESCRIPTION_DATABASE_USER,
               login=database_user.login, domain=database_user.domain_id, account_id=database_user.account_id
           )
           for database_user in self.get_selected_database_users().values()
        })

        selected_accounts_ids += [
            user.account_id
            for user in self.get_selected_protected_directories_users().values()
            if user.account_id != '0'
        ]
        object_descriptions.update({
            user.account_id: safe_format(
                messages.OBJECT_DESCRIPTION_PROTECTED_DIR_USER, login=user.login, account_id=user.account_id
            )
            for user in self.get_selected_protected_directories_users().values()
            if user.account_id != '0'
        })

        accounts = {}
        for accounts_ids in self._iter_ranges(selected_accounts_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `password`, `type`
                FROM `accounts`
                WHERE `id` IN ({accounts_ids})
            """.format(accounts_ids=format_ids_list(accounts_ids)), log_output=False)
            for row in rows:
                accounts[row['id']] = Account(
                    account_id=row['id'], password=row['password'], password_type=row['type'],
                )

        encrypted_passwords = [
            EncryptedPasswordItem(
                id_object=account.account_id,
                description=object_descriptions.get(account.account_id, account.account_id),
                value=account.password,
            ) for account in accounts.values() if account.password_type == 'sym'
        ]

        source_server = self._dump_agent.source_server()
        password_decryptor = PleskDecryptor(source_server)
        decrypted_password_items, decrypted_password_errors = password_decryptor.decrypt_data(encrypted_passwords)

        for decrypted_password_item in decrypted_password_items:
            account_id = decrypted_password_item.id_object
            account = accounts.get(account_id)
            if account:
                properties = account.as_dictionary()
                properties['password'] = decrypted_password_item.value
                properties['password_type'] = 'plain'
                accounts[account_id] = Account(**properties)

        for decrypted_password_error in decrypted_password_errors:
            account_id = decrypted_password_error.id_object
            object_description = object_descriptions.get(account_id, account_id)

            logger.fwarn(
                messages.FAILED_TO_DECRYPT_ACCOUNT_PASSWORD,
                object_description=object_description
            )

            self._dump_agent.global_context.pre_check_report.add_issue(
                'decrypt_password',
                Issue.SEVERITY_WARNING,
                safe_format(
                    messages.FAILED_TO_DECRYPT_ACCOUNT_PASSWORD,
                    object_description=object_description
                )
            )

        return accounts

    @cached
    @data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_USERS, {})
    def get_selected_users(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.user.User]
        """
        users = []

        external_email_field = ", `externalEmail`" if int(self._dump_agent.execute_sql("""
        SELECT count(*) as count
          FROM information_schema.COLUMNS
          WHERE TABLE_SCHEMA = 'psa'
          AND TABLE_NAME = 'smb_users'
          AND COLUMN_NAME = 'externalEmail';
        """)[0]['count']) > 0 else ""

        for clients_ids in self._iter_ranges(self._get_selected_clients_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `ownerId`, `subscriptionDomainId`, `roleId`, `login`, `password`, `uuid`, `locale`,
                    `creationDate`, `isLocked`, `isBuiltIn`, `contactName`, `email`, `companyName`, `country`, `zip`,
                    `state`, `city`, `address`, `phone`, `fax`, `imType`, `imNumber`, `additionalInfo`{external_email_field}
                FROM `smb_users`
                WHERE `ownerId` IN ({users_ids})
            """.format(external_email_field=external_email_field, users_ids=format_ids_list(clients_ids)), log_output=False)
            for row in rows:
                users.append(User(
                    user_id=row['id'], owner_id=row['ownerId'], subscription_domain_id=row['subscriptionDomainId'],
                    role_id=row['roleId'], login=row['login'], password=row['password'], password_type='sym',
                    guid=row['uuid'], locale=row['locale'], creation_date=row['creationDate'],
                    is_locked=row['isLocked'], is_built_in=row['isBuiltIn'], contact_name=row['contactName'],
                    email=row['email'], company_name=row['companyName'], country=row['country'], postal_code=row['zip'],
                    state=row['state'], city=row['city'], address=row['address'], phone=row['phone'], fax=row['fax'],
                    im_type=row['imType'], im_number=row['imNumber'], additional_info=row['additionalInfo'],
                    external_email='' if row.get('externalEmail', 'None') == 'None' else row['externalEmail'],
                ))

        selected_users = {}
        for user in users:
            if (
                # user has access to some specific subscription
                (user.subscription_domain_id is not None and str(user.subscription_domain_id) != '0') and
                # and the subscription is among the subscriptions which are going to be migrated
                user.subscription_domain_id not in self._get_selected_domain_ids()
            ):
                continue
            selected_users[user.login] = user

        if self.get_misc_parameters().get('secure_passwords') == 'true':
            encrypted_passwords = [
                EncryptedPasswordItem(
                    id_object=user.login,
                    description=safe_format(messages.OBJECT_DESCRIPTION_AUX_USER, login=user.login),
                    value=user.password,
                ) for user in selected_users.values() if user.password
            ]

            source_server = self._dump_agent.source_server()
            password_decryptor = PleskDecryptor(source_server)
            decrypted_password_items, decrypted_password_errors = password_decryptor.decrypt_data(
                encrypted_passwords
            )

            for decrypted_password_item in decrypted_password_items:
                login = decrypted_password_item.id_object
                user = selected_users.get(login)
                if user:
                    properties = user.as_dictionary()
                    properties['password'] = decrypted_password_item.value
                    properties['password_type'] = 'plain'
                    selected_users[login] = User(**properties)

            for decrypted_password_error in decrypted_password_errors:
                login = decrypted_password_error.id_object
                object_description = safe_format(messages.OBJECT_DESCRIPTION_AUX_USER, login=login)

                logger.fwarn(
                    messages.FAILED_TO_DECRYPT_ACCOUNT_PASSWORD,
                    object_description=object_description
                )

                self._dump_agent.global_context.pre_check_report.add_issue(
                    'decrypt_password',
                    Issue.SEVERITY_WARNING,
                    safe_format(
                        messages.FAILED_TO_DECRYPT_ACCOUNT_PASSWORD,
                        object_description=object_description
                    )
                )
        else:
            failed_users = []
            for selected_user in selected_users.values():
                properties = selected_user.as_dictionary()

                password = None
                password_type = None

                decoded_password = None
                try:
                    decoded_password = base64_decode(selected_user.password)
                except Exception:
                    logger.exception()

                if decoded_password is not None and is_unicode_or_utf8_string(decoded_password):
                    password = decoded_password
                    password_type = 'plain'
                else:
                    failed_users.append(selected_user.login)

                properties['password'] = password
                properties['password_type'] = password_type
                selected_users[selected_user.login] = User(**properties)
            if failed_users:
                self._dump_agent.global_context.pre_check_report.add_issue(
                    'dump_data',
                    Issue.SEVERITY_WARNING,
                    safe_format(messages.FAILED_TO_DECODE_SUBSCRIPTIONS_USERS_PASSWORDS, users=', '.join(failed_users))
                )
        return selected_users

    @cached
    @data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_USERS_ROLES, {})
    def get_selected_roles(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.role.Role]
        """
        selected_roles_ids = set()
        for user in self.get_selected_users().values():
            selected_roles_ids.add(user.role_id)
        roles = {}
        for roles_ids in self._iter_ranges(selected_roles_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `ownerId`, `name`, `isBuiltIn`
                FROM `smb_roles`
                WHERE `id` IN ({roles_ids})
            """.format(roles_ids=format_ids_list(roles_ids)))
            for row in rows:
                roles[row['id']] = Role(
                    role_id=row['id'], owner_id=row['ownerId'], name=row['name'], is_built_in=row['isBuiltIn'],
                )
        return roles

    @cached
    @data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_USERS_ROLES, {})
    def get_selected_roles_general_permissions(self):
        """
        :rtype: dict[str|unicode, list[parallels.plesk.source.plesk.hosting_description.plesk_entity.role_general_permission.RoleGeneralPermission]]
        """
        permissions = defaultdict(list)
        for roles_ids in self._iter_ranges(self.get_selected_roles().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `roleId`, `generalPermissionId`, `isAllowed`
                FROM `smb_roleGeneralPermissions`
                WHERE `roleId` IN ({roles_ids})
            """.format(roles_ids=format_ids_list(roles_ids)))
            for row in rows:
                permissions[row['roleId']].append(RoleGeneralPermission(
                    role_id=row['roleId'], general_permission_id=row['generalPermissionId'],
                    is_allowed=row['isAllowed'],
                ))
        return permissions

    @cached
    @data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_USERS_ROLES, {})
    def get_selected_roles_service_permissions(self):
        """
        :rtype: dict[str|unicode, list[parallels.plesk.source.plesk.hosting_description.plesk_entity.role_service_permission.RoleServicePermission]]
        """
        permissions = defaultdict(list)
        for roles_ids in self._iter_ranges(self.get_selected_roles().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `roleId`, `servicePermissionId`
                FROM `smb_roleServicePermissions`
                WHERE `roleId` IN ({roles_ids})
            """.format(roles_ids=format_ids_list(roles_ids)))
            for row in rows:
                permissions[row['roleId']].append(RoleServicePermission(
                    role_id=row['roleId'], service_permission_id=row['servicePermissionId'],
                ))
        return permissions

    @cached
    @data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_USERS_ROLES, {})
    def get_selected_service_permissions(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.service_permission.ServicePermission]
        """
        selected_permissions_ids = []
        for role_service_permissions in self.get_selected_roles_service_permissions().values():
            for role_service_permission in role_service_permissions:
                selected_permissions_ids.append(role_service_permission.service_permission_id)
        permissions = {}
        for permissions_ids in self._iter_ranges(selected_permissions_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `serviceInstanceId`, `serviceProviderId`, `permissionCode`, `class`
                FROM `smb_servicePermissions`
                WHERE `id` IN ({permissions_ids})
            """.format(permissions_ids=format_ids_list(permissions_ids)))
            for row in rows:
                permissions[row['id']] = ServicePermission(
                    service_permission_id=row['id'], service_instance_id=row['serviceInstanceId'],
                    service_provider_id=row['serviceProviderId'], permission_code=row['permissionCode'],
                    permission_class=row['class'],
                )
        return permissions

    @cached
    @data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_USERS_ROLES, {})
    def get_selected_service_instances(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.service_instance.ServiceInstance]
        """
        selected_instances_ids = [
            service_permission.service_instance_id
            for service_permission in self.get_selected_service_permissions().values()
        ]
        instances = {}
        for instances_ids in self._iter_ranges(selected_instances_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `serviceProviderId`, `externalId`, `description`
                FROM `smb_serviceInstances`
                WHERE `id` IN ({instances_ids})
            """.format(instances_ids=format_ids_list(instances_ids)))
            for row in rows:
                instances[row['id']] = ServiceInstance(
                    service_instance_id=row['id'], service_provider_id=row['serviceProviderId'],
                    external_id=row['externalId'], description=row['description'],
                )
        return instances

    @cached
    @data_dump(messages.DATA_DUMP_CUSTOM_BUTTONS, {})
    def get_selected_custom_buttons(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.custom_button.CustomButton]
        """
        custom_buttons = {}

        def get_level_custom_buttons(level, level_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `level_id`, `level`, `url`, `options`, `text`, `file`, `conhelp`, `sort_key`, `place`
                FROM `custom_buttons`
                WHERE `level` = {level} AND `level_id` IN ({level_ids})
            """.format(level=level, level_ids=format_ids_list(level_ids)))
            return {
                row['id']: CustomButton(
                    custom_button_id=row['id'], level_id=row['level_id'], level=row['level'], url=row['url'],
                    options=row['options'], text=row['text'], image_file=row['file'], context_help=row['conhelp'],
                    sort_key=row['sort_key'], place=row['place'],
                )
                for row in rows
            }

        selected_resellers_ids = [client.client_id for client in self.get_clients(client_type='reseller')]
        for resellers_ids in self._iter_ranges(selected_resellers_ids):
            custom_buttons.update(get_level_custom_buttons(CustomButton.LEVEL_RESELLER, resellers_ids))
        selected_clients_ids = [client.client_id for client in self.get_clients(client_type='client')]
        for clients_ids in self._iter_ranges(selected_clients_ids):
            custom_buttons.update(get_level_custom_buttons(CustomButton.LEVEL_CLIENT, clients_ids))

        return custom_buttons

    @cached
    @data_dump(messages.DATA_DUMP_IIS_APPLICATIONS_POOLS_SETTINGS, {})
    def get_selected_iis_app_pools(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.iis_app_pool.IisAppPool]
        """
        iis_app_pools = {}

        def get_objects_iis_app_pools(object_type, object_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `ownerId`, `ownerType`, `isStarted`, `identity`, `maxProcesses`, `cpuLimit`,
                    `cpuLimitAction`, `cpuLimitInterval`, `idleTimeout`, `idleTimeoutAction`, `recyclingByTime`,
                    `recyclingByRequests`, `recyclingByVirtualMemory`, `recyclingByPrivateMemory`
                FROM `IisAppPools`
                WHERE `ownerType` = '{object_type}' AND `ownerId` IN ({object_ids})
            """.format(object_type=object_type, object_ids=format_ids_list(object_ids)))
            return {
                row['id']: IisAppPool(
                    iis_app_pool_id=row['id'], owner_id=row['ownerId'], owner_type=row['ownerType'],
                    is_started=row['isStarted'], identity=row['identity'], max_processes=row['maxProcesses'],
                    cpu_limit=row['cpuLimit'], cpu_limit_action=row['cpuLimitAction'],
                    cpu_limit_interval=row['cpuLimitInterval'], idle_timeout=row['idleTimeout'],
                    idle_timeout_action=row['idleTimeoutAction'], recycling_by_time=row['recyclingByTime'],
                    recycling_by_requests=row['recyclingByRequests'],
                    recycling_by_virtual_memory=row['recyclingByVirtualMemory'],
                    recycling_by_private_memory=row['recyclingByPrivateMemory'],
                    managed_pipeline_mode=None,
                )
                for row in rows
            }

        for clients_ids in self._iter_ranges(self._get_selected_clients_ids()):
            iis_app_pools.update(get_objects_iis_app_pools('client', clients_ids))
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            iis_app_pools.update(get_objects_iis_app_pools('domain', domain_ids))

        return iis_app_pools

    @cached
    @data_dump(messages.DATA_DUMP_DOMAINS, {})
    def get_selected_domains(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain]
        """
        domains = {}
        for domains_ids in self._iter_ranges(self._get_selected_domain_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `parentDomainId`, `webspace_id`, `cl_id`, `dns_zone_id`, `cert_rep_id`,
                    `displayName`, `name`, `guid`, `external_id`, `cr_date`, `status`, `webspace_status`, `htype`,
                    `description`, `resellerDescription`, `adminDescription`
                FROM `domains`
                WHERE `id` IN ({domains_ids})
            """.format(domains_ids=format_ids_list(domains_ids)))
            for row in rows:
                domains[row['id']] = Domain(
                    domain_id=row['id'], parent_domain_id=row['parentDomainId'], webspace_id=row['webspace_id'],
                    owner_id=row['cl_id'], dns_zone_id=row['dns_zone_id'], cert_rep_id=row['cert_rep_id'],
                    name=row['displayName'], ascii_name=row['name'], guid=row['guid'], external_id=row['external_id'],
                    creation_date=row['cr_date'], status=row['status'], webspace_status=row['webspace_status'],
                    hosting_type=row['htype'], description=row['description'],
                    reseller_description=row['resellerDescription'], admin_description=row['adminDescription'],
                )
        return domains

    @cached
    @data_dump(messages.DATA_DUMP_WEB_HOSTING_SETTINGS, {})
    def get_selected_hosting(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.hosting.Hosting]
        """
        selected_hosting_domain_ids = [
            domain.domain_id
            for domain in self.get_selected_domains().values() if domain.hosting_type == 'vrt_hst'
        ]
        hosting = {}
        for domains_ids in self._iter_ranges(selected_hosting_domain_ids):
            if self._dump_agent.source_server().is_windows():
                rows = self._dump_agent.execute_sql("""
                    SELECT `dom_id`, `sys_user_id`, `certificate_id`, `ssi`, `ssi_html`, `php`, `php_handler_id`, `cgi`,
                        `perl`, `python`, `asp`, `asp_dot_net`, `managed_runtime_version`, `ssl`, `webstat`,
                        `write_modify`, `www_root`, `webdeploy`, `traffic_bandwidth`, `max_connection`
                    FROM `hosting`
                    WHERE `dom_id` IN ({domains_ids})
                """.format(domains_ids=format_ids_list(domains_ids)))
                for row in rows:
                    hosting[row['dom_id']] = Hosting(
                        domain_id=row['dom_id'], sys_user_id=row['sys_user_id'], certificate_id=row['certificate_id'],
                        ssi=row['ssi'], ssi_html=row['ssi_html'], php=row['php'], php_handler_id=row['php_handler_id'],
                        cgi=row['cgi'], perl=row['perl'], python=row['python'], asp=row['asp'],
                        asp_dot_net=row['asp_dot_net'], managed_runtime_version=row['managed_runtime_version'],
                        ssl=row['ssl'], webstat=row['webstat'], write_modify=row['write_modify'],
                        www_root=row['www_root'], webdeploy=row['webdeploy'], traffic_bandwidth=row['traffic_bandwidth'],
                        max_connection=row['max_connection'], fastcgi=None,
                    )
            else:
                rows = self._dump_agent.execute_sql("""
                    SELECT `dom_id`, `sys_user_id`, `certificate_id`, `ssi`, `ssi_html`, `php`, `php_handler_id`, `cgi`,
                        `perl`, `python`, `asp`, `asp_dot_net`, `managed_runtime_version`, `ssl`, `webstat`,
                        `write_modify`, `www_root`, `webdeploy`, `traffic_bandwidth`, `max_connection`, `fastcgi`
                    FROM `hosting`
                    WHERE `dom_id` IN ({domains_ids})
                """.format(domains_ids=format_ids_list(domains_ids)))
                for row in rows:
                    hosting[row['dom_id']] = Hosting(
                        domain_id=row['dom_id'], sys_user_id=row['sys_user_id'], certificate_id=row['certificate_id'],
                        ssi=row['ssi'], ssi_html=row['ssi_html'], php=row['php'], php_handler_id=row['php_handler_id'],
                        cgi=row['cgi'], perl=row['perl'], python=row['python'], asp=row['asp'],
                        asp_dot_net=row['asp_dot_net'], managed_runtime_version=row['managed_runtime_version'],
                        ssl=row['ssl'], webstat=row['webstat'], write_modify=row['write_modify'],
                        www_root=row['www_root'], webdeploy=row['webdeploy'], traffic_bandwidth=row['traffic_bandwidth'],
                        max_connection=row['max_connection'], fastcgi=row['fastcgi'],
                    )
        return hosting

    @cached
    @data_dump(messages.DATA_DUMP_WEB_HOSTING_SETTINGS, {})
    def get_selected_forwarding(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.forwarding.Forwarding]
        """
        selected_forwarding_domain_ids = [
            domain.domain_id
            for domain in self.get_selected_domains().values() if domain.hosting_type in ['std_fwd', 'frm_fwd']
        ]
        forwarding = {}
        for domains_ids in self._iter_ranges(selected_forwarding_domain_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `dom_id`, `redirect`, `http_code`
                FROM `forwarding`
                WHERE `dom_id` IN ({domains_ids})
            """.format(domains_ids=format_ids_list(domains_ids)))
            for row in rows:
                forwarding[row['dom_id']] = Forwarding(
                    domain_id=row['dom_id'], redirect=row['redirect'], http_code=row['http_code'],
                )
        return forwarding

    @cached
    @data_dump(messages.DATA_DUMP_DOMAINS_SERVICES_SETTINGS, {})
    def get_selected_domain_services(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.domain_service.DomainService]
        """
        domain_services = {}
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `dom_id`, `ipCollectionId`, `parameters_id`, `type`, `status`
                FROM `DomainServices`
                WHERE `dom_id` IN ({domain_ids})
            """.format(domain_ids=format_ids_list(domain_ids)))
            for row in rows:
                domain_services[row['id']] = DomainService(
                    domain_service_id=row['id'], domain_id=row['dom_id'], ip_collection_id=row['ipCollectionId'],
                    parameters_id=row['parameters_id'], service_type=row['type'], status=row['status'],
                )
        return domain_services

    @cached
    @data_dump(messages.DATA_DUMP_SYSTEM_USERS, {})
    def get_selected_sys_users(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.sys_user.SysUser]
        """
        selected_sys_users_ids = [
            hosting.sys_user_id
            for hosting in self.get_selected_hosting().values()
        ]
        selected_sys_users_ids += [
            ftp_user.sys_user_id
            for ftp_user in self.get_selected_ftp_users().values()
        ]
        selected_sys_users_ids += [
            web_user.sys_user_id
            for web_user in self.get_selected_web_users().values()
        ]
        sys_users = {}
        for sys_users_ids in self._iter_ranges(selected_sys_users_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `account_id`, `login`, `home`, `shell`, `quota`
                FROM `sys_users`
                WHERE `id` IN ({sys_users_ids})
            """.format(sys_users_ids=format_ids_list(sys_users_ids)))
            for row in rows:
                sys_users[row['id']] = SysUser(
                    sys_user_id=row['id'], account_id=row['account_id'], login=row['login'], home=row['home'],
                    shell=row['shell'], quota=row['quota'],
                )
        return sys_users

    @cached
    @data_dump(messages.DATA_DUMP_FTP_USERS, {})
    def get_selected_ftp_users(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.ftp_user.FtpUser]
        """
        selected_domains_ids = []
        for domain in self.get_domains(webspace_id='0'):
            hosting = self.get_selected_hosting().get(domain.domain_id)
            if hosting:
                selected_domains_ids.append(hosting.domain_id)
        ftp_users = {}
        for domains_ids in self._iter_ranges(selected_domains_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `dom_id`, `sys_user_id`, `permission`
                FROM `ftp_users`
                WHERE `dom_id` IN ({domains_ids})
            """.format(domains_ids=format_ids_list(domains_ids)))
            for row in rows:
                ftp_users[row['id']] = FtpUser(
                    ftp_user_id=row['id'], domain_id=row['dom_id'], sys_user_id=row['sys_user_id'],
                    permission=row['permission'],
                )
        return ftp_users

    @cached
    @data_dump(messages.DATA_DUMP_WEB_USERS, {})
    def get_selected_web_users(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.web_user.WebUser]
        """
        selected_domains_ids = []
        for domain in self.get_domains(webspace_id='0'):
            hosting = self.get_selected_hosting().get(domain.domain_id)
            if hosting:
                selected_domains_ids.append(hosting.domain_id)
        web_users = {}
        for domains_ids in self._iter_ranges(selected_domains_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `dom_id`, `sys_user_id`, `php`, `asp_dot_net`, `write_modify`, `cgi`, `fastcgi`
                FROM `web_users`
                WHERE `dom_id` IN ({domains_ids})
            """.format(domains_ids=format_ids_list(domains_ids)))
            for row in rows:
                web_users[row['id']] = WebUser(
                    web_user_id=row['id'], domain_id=row['dom_id'], sys_user_id=row['sys_user_id'],
                    php=row['php'], asp_dot_net=row['asp_dot_net'], write_modify=row['write_modify'],
                    cgi=row['cgi'], fastcgi=row['fastcgi'],
                )
        return web_users

    @cached
    @data_dump(messages.DATA_DUMP_MAIL_SETTINGS, {})
    def get_selected_parameters(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        selected_parameters_ids = {
            domain_service.parameters_id
            for domain_service in self.get_selected_domain_services().values()
            if domain_service.parameters_id not in [None, '0']
        }
        parameters = defaultdict(dict)
        for parameters_ids in self._iter_ranges(selected_parameters_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `parameter`, `value`
                FROM `Parameters`
                WHERE `id` IN ({parameters_ids})
            """.format(parameters_ids=format_ids_list(parameters_ids)))
            for row in rows:
                parameters[row['id']][row['parameter']] = row['value']
        if self._dump_agent.source_server().is_windows():
            for parameters_ids in self._iter_ranges(selected_parameters_ids):
                rows = self._dump_agent.execute_sql("""
                    SELECT `id`, `parameter`, `value`
                    FROM `bigparameters`
                    WHERE `id` IN ({parameters_ids})
                """.format(parameters_ids=format_ids_list(parameters_ids)))
                for row in rows:
                    parameters[row['id']][row['parameter']] = row['value']
        return parameters

    @cached
    @data_dump(messages.DATA_DUMP_DOMAINS_SERVICES_SETTINGS, {})
    def get_selected_domain_parameters(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        parameters = defaultdict(dict)
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `dom_id`, `param`, `val`
                FROM `dom_param`
                WHERE `dom_id` IN ({domain_ids})
            """.format(domain_ids=format_ids_list(domain_ids)))
            for row in rows:
                parameters[row['dom_id']][row['param']] = row['val']
        return parameters

    @cached
    @data_dump(messages.DATA_DUMP_LIMITS, {})
    def get_selected_limits(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        selected_limits_ids = {client.limits_id for client in self.get_clients(client_type='reseller')}
        for subscription_properties in self.get_selected_subscriptions_properties().values():
            limits_id = subscription_properties.get('limitsId')
            if limits_id is not None and limits_id.isdigit():
                selected_limits_ids.add(limits_id)
        limits = defaultdict(dict)
        for limits_ids in self._iter_ranges(selected_limits_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `limit_name`, `value`
                FROM `Limits`
                WHERE `id` IN ({limits_ids})
            """.format(limits_ids=format_ids_list(limits_ids)))
            for row in rows:
                limits[row['id']][row['limit_name']] = row['value']
        return limits

    @cached
    @data_dump(messages.DATA_DUMP_CL_PARAMS, {})
    def get_selected_cl_params(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        selected_clients_ids = [client.client_id for client in self.get_clients(client_type='reseller')]
        cl_params = defaultdict(dict)
        for clients_ids in self._iter_ranges(selected_clients_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `cl_id`, `param`, `val`
                FROM `cl_param`
                WHERE `cl_id` IN ({clients_ids})
            """.format(clients_ids=format_ids_list(clients_ids)))
            for row in rows:
                cl_params[row['cl_id']][row['param']] = row['val']
        return cl_params


    @cached
    @data_dump(messages.DATA_DUMP_PERMISSIONS, {})
    def get_selected_permissions(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        selected_permissions_ids = {client.permissions_id for client in self.get_clients(client_type='reseller')}
        for subscription_properties in self.get_selected_subscriptions_properties().values():
            permissions_id = subscription_properties.get('permissionsId')
            if permissions_id is not None and permissions_id.isdigit():
                selected_permissions_ids.add(permissions_id)
        permissions = defaultdict(dict)
        for permissions_ids in self._iter_ranges(selected_permissions_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `permission`, `value`
                FROM `Permissions`
                WHERE `id` IN ({permissions_ids})
            """.format(permissions_ids=format_ids_list(permissions_ids)))
            for row in rows:
                permissions[row['id']][row['permission']] = row['value']
        return permissions

    @cached
    def get_selected_mail_names(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_name.MailName]
        """
        return {
            mail_name.mail_name_id: mail_name
            for mail_name in self._get_all_mail_names().values()
            if mail_name.domain_id in self._get_selected_domain_ids()
        }

    @cached
    @data_dump(messages.DATA_DUMP_MAIL_ALIASES, {})
    def get_selected_mail_aliases(self):
        """
        :rtype: dict[str|unicode, list[str|unicode]]
        """
        mail_aliases = defaultdict(list)
        for mail_names_ids in self._iter_ranges(self.get_selected_mail_names().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `mn_id`, `alias`
                FROM `mail_aliases`
                WHERE `mn_id` IN ({mail_names_ids})
            """.format(mail_names_ids=format_ids_list(mail_names_ids)))
            for row in rows:
                mail_aliases[row['mn_id']].append(row['alias'])
        return mail_aliases

    @cached
    @data_dump(messages.DATA_DUMP_MAIL_FORWARDING, {})
    def get_selected_mail_redirects(self):
        """
        :rtype: dict[str|unicode, list[str|unicode]]
        """
        mail_redirects = defaultdict(list)
        for mail_names_ids in self._iter_ranges(self.get_selected_mail_names().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `mn_id`, `address`
                FROM `mail_redir`
                WHERE `mn_id` IN ({mail_names_ids})
            """.format(mail_names_ids=format_ids_list(mail_names_ids)))
            for row in rows:
                mail_redirects[row['mn_id']].append(row['address'])
        return mail_redirects

    @cached
    @data_dump(messages.DATA_DUMP_MAIL_SETTINGS, {})
    def get_selected_mail_names_parameters(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        parameters = defaultdict(dict)
        for mail_names_ids in self._iter_ranges(self.get_selected_mail_names().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `mn_id`, `param`, `val`
                FROM `mn_param`
                WHERE `mn_id` IN ({mail_names_ids})
            """.format(mail_names_ids=format_ids_list(mail_names_ids)))
            for row in rows:
                parameters[row['mn_id']][row['param']] = row['val']
        return parameters

    @cached
    @data_dump(messages.DATA_DUMP_MAIL_AUTO_REPLY_SETTINGS, {})
    def get_selected_mail_responders(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_responder.MailResponder]
        """
        mail_responders = {}
        if self._dump_agent.source_server().is_windows():
            for mail_names_ids in self._iter_ranges(self.get_selected_mail_names().keys()):
                rows = self._dump_agent.execute_sql("""
                    SELECT `id`, `mn_id`, `subject`, `text`, `ans_freq`, `ishtml`
                    FROM `mail_resp`
                    WHERE `mn_id` IN ({mail_names_ids})
                """.format(mail_names_ids=format_ids_list(mail_names_ids)))
                for row in rows:
                    mail_responders[row['id']] = MailResponder(
                        mail_responder_id=row['id'], mail_name_id=row['mn_id'], subject=row['subject'],
                        text=row['text'], ans_freq=row['ans_freq'],
                        content_type='text/html' if row['ishtml'] == 'true' else 'text/plain',
                        end_date=None,
                    )
        else:
            for mail_names_ids in self._iter_ranges(self.get_selected_mail_names().keys()):
                rows = self._dump_agent.execute_sql("""
                    SELECT `id`, `mn_id`, `subject`, `text`, `ans_freq`, `content_type`
                    FROM `mail_resp`
                    WHERE `mn_id` IN ({mail_names_ids})
                """.format(mail_names_ids=format_ids_list(mail_names_ids)))
                for row in rows:
                    mail_responders[row['id']] = MailResponder(
                        mail_responder_id=row['id'], mail_name_id=row['mn_id'], subject=row['subject'],
                        text=row['text'], ans_freq=row['ans_freq'],
                        content_type=row['content_type'],
                        end_date=None,
                    )
        return mail_responders

    @cached
    @data_dump(messages.DATA_DUMP_MAIL_AUTO_REPLY_SETTINGS, {})
    def get_selected_mail_responders_forwarding(self):
        """
        :rtype: dict[str|unicode, list[str|unicode]]
        """
        forwarding = defaultdict(list)
        for responders_ids in self._iter_ranges(self.get_selected_mail_responders().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `rn_id`, `address`
                FROM `resp_forward`
                WHERE `rn_id` IN ({responders_ids})
            """.format(responders_ids=format_ids_list(responders_ids)))
            for row in rows:
                forwarding[row['rn_id']].append(row['address'])
        return forwarding

    @cached
    @data_dump(messages.DATA_DUMP_MAIL_AUTO_REPLY_SETTINGS, {})
    def get_selected_mail_responders_attachments(self):
        """
        :rtype: dict[str|unicode, list[str|unicode]]
        """
        attachments = defaultdict(list)
        for responders_ids in self._iter_ranges(self.get_selected_mail_responders().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `rn_id`, `filename`
                FROM `resp_attach`
                WHERE `rn_id` IN ({responders_ids})
            """.format(responders_ids=format_ids_list(responders_ids)))
            for row in rows:
                attachments[row['rn_id']].append(row['filename'])
        return attachments

    @cached
    @data_dump(messages.DATA_DUMP_MAILLISTS, {})
    def get_selected_mail_lists(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_list.MailList]
        """
        mail_lists = {}
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            if self._dump_agent.source_server().is_windows():
                rows = self._dump_agent.execute_sql("""
                    SELECT `id`, `dom_id`, `name`, `status`, `admin_email`
                    FROM `MailLists`
                    WHERE `dom_id` IN ({domain_ids})
                """.format(domain_ids=format_ids_list(domain_ids)))
                for row in rows:
                    mail_lists[row['id']] = MailList(
                        mail_list_id=row['id'], domain_id=row['dom_id'], name=row['name'], status=row['status'],
                        admin_email=row['admin_email'],
                    )
            else:
                rows = self._dump_agent.execute_sql("""
                    SELECT `id`, `dom_id`, `name`, `status`
                    FROM `MailLists`
                    WHERE `dom_id` IN ({domain_ids})
                """.format(domain_ids=format_ids_list(domain_ids)))
                for row in rows:
                    mail_lists[row['id']] = MailList(
                        mail_list_id=row['id'], domain_id=row['dom_id'], name=row['name'], status=row['status'],
                        admin_email=None,
                    )
        return mail_lists

    @cached
    @data_dump(messages.DATA_DUMP_MAILLISTS_SETTINGS, {})
    def get_selected_mail_lists_settings(self):
        """
        :rtype: dict
        """
        settings = defaultdict(dict)
        errors = []
        with self._dump_agent.source_server().runner() as runner:
            assert isinstance(runner, BaseRunner)
            if self._dump_agent.source_server().is_windows():
                for mail_list in self.get_selected_mail_lists().values():
                    domain = self.get_selected_domains().get(mail_list.domain_id)
                    if not domain:
                        continue
                    list_settings = {
                        'owners': [mail_list.admin_email if mail_list.admin_email else 'root@localhost'],
                        'subscribers': [],
                    }
                    try:
                        command = '{listmng} list-members {list_name} {domain_name}'
                        command_args = dict(
                            listmng=ntpath.join(self._dump_agent.source_server().plesk_dir, r'admin\bin\listmng'),
                            list_name=mail_list.name,
                            domain_name=domain.name.encode('idna'),
                        )
                        stdout = runner.execute_command(command, command_args).stdout
                        list_settings['subscribers'] = [
                            safe_mail_idn_decode(subscriber) for subscriber in stdout.splitlines()
                        ]
                    except Exception as e:
                        logger.exception()
                        errors.append(safe_format(
                            messages.FAILED_TO_GET_MAILLIST_SUBSCRIBERS,
                            name='%s@%s' % (mail_list.name, domain.name),
                            error=e
                        ))
                    settings[mail_list.domain_id][mail_list.name] = list_settings
            else:
                mailman_backup_name = "mailman_backup.py"
                remote_mailman_backup_path = self._dump_agent.source_server().get_session_file_path(mailman_backup_name)
                runner.upload_file(
                    migrator_utils.get_package_extras_file_path(parallels.plesk.source.plesk, mailman_backup_name),
                    remote_mailman_backup_path
                )
                remote_maillists_list_path = self._dump_agent.source_server().get_session_file_path(
                    'maillists.list'
                )
                runner.upload_file_content(
                    remote_maillists_list_path,
                    "\n".join([mail_list.name for mail_list in self.get_selected_mail_lists().values()])
                )
                command = 'python {mailman_backup} --mailman-lib={mailman_lib_path} --lists-from={filter_path}'
                command_args = dict(
                    mailman_backup=remote_mailman_backup_path,
                    mailman_lib_path=self._dump_agent.source_server().mailman_root_dir,
                    filter_path=remote_maillists_list_path,
                )
                stdout = runner.execute_command(command, command_args).stdout
                result = ast.literal_eval(stdout)
                for list_name, list_settings in result.items():
                    if 'error' in list_settings:
                        errors.append(list_settings['error'])
                        continue
                    mail_lists = self.get_mail_lists(list_name=list_name)
                    if not mail_lists:
                        continue
                    mail_list = mail_lists[0]
                    list_settings['owners'] = [
                        safe_mail_idn_decode(owner) for owner in list_settings['owners']
                    ]
                    list_settings['subscribers'] = [
                        safe_mail_idn_decode(subscriber) for subscriber in list_settings['subscribers']
                    ]
                    settings[mail_list.domain_id][mail_list.name] = list_settings
        if errors:
            self._dump_agent.global_context.pre_check_report.add_issue(
                'dump_data',
                Issue.SEVERITY_WARNING,
                safe_format(messages.MAILLISTS_SETTINGS_DUMPING_ERRORS, errors="\n".join(errors))
            )
        return settings

    @cached
    @data_dump(messages.DATA_DUMP_DOMAIN_ALIASES, {})
    def get_selected_domain_aliases(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.domain_alias.DomainAlias]
        """
        domain_aliases = {}
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `dom_id`, `dns_zone_id`, `displayName`, `status`, `mail`, `web`, `dns`, `tomcat`,
                    `seoRedirect`
                FROM `domain_aliases`
                WHERE `dom_id` IN ({domain_ids})
            """.format(domain_ids=format_ids_list(domain_ids)))
            for row in rows:
                domain_aliases[row['id']] = DomainAlias(
                    domain_alias_id=row['id'], domain_id=row['dom_id'], dns_zone_id=row['dns_zone_id'],
                    name=row['displayName'], status=row['status'], mail=row['mail'], web=row['web'],
                    dns=row['dns'], tomcat=row['tomcat'], seo_redirect=row['seoRedirect'],
                )
        return domain_aliases

    @cached
    @data_dump(messages.DATA_DUMP_DNS_ZONES, {})
    def get_selected_dns_zones(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.dns_zone.DnsZone]
        """
        selected_dns_zones_ids = [
            domain.dns_zone_id
            for domain in self.get_selected_domains().values()
        ]
        selected_dns_zones_ids += [
            domain_alias.dns_zone_id
            for domain_alias in self.get_selected_domain_aliases().values()
        ]
        dns_zones = {}
        for dns_zones_ids in self._iter_ranges(selected_dns_zones_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `displayName`, `email`, `status`, `type`, `ttl`, `ttl_unit`, `refresh`, `refresh_unit`,
                    `retry`, `retry_unit`, `expire`, `expire_unit`, `minimum`, `minimum_unit`, `serial_format`
                FROM `dns_zone`
                WHERE `id` IN ({dns_zones_ids})
            """.format(dns_zones_ids=format_ids_list(dns_zones_ids)))
            for row in rows:
                dns_zones[row['id']] = DnsZone(
                    dns_zone_id=row['id'], name=row['displayName'], email=row['email'], status=row['status'],
                    dns_zone_type=row['type'], ttl=row['ttl'], ttl_unit=row['ttl_unit'], refresh=row['refresh'],
                    refresh_unit=row['refresh_unit'], retry=row['retry'], retry_unit=row['retry_unit'],
                    expire=row['expire'], expire_unit=row['expire_unit'], minimum=row['minimum'],
                    minimum_unit=row['minimum_unit'], serial_format=row['serial_format'],
                )
        return dns_zones

    @cached
    @data_dump(messages.DATA_DUMP_DNS_RECORDS, {})
    def get_selected_dns_records(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.dns_record.DnsRecord]
        """
        dns_records = {}
        for dns_zones_ids in self._iter_ranges(self.get_selected_dns_zones().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `dns_zone_id`, `type`, `displayHost`, `displayVal`, `opt`
                FROM `dns_recs`
                WHERE `dns_zone_id` IN ({dns_zones_ids})
            """.format(dns_zones_ids=format_ids_list(dns_zones_ids)))
            for row in rows:
                dns_records[row['id']] = DnsRecord(
                    dns_record_id=row['id'], dns_zone_id=row['dns_zone_id'], dns_record_type=row['type'],
                    host=row['displayHost'], value=row['displayVal'], opt=row['opt'],
                )
        return dns_records

    @cached
    @data_dump(messages.DATA_DUMP_DATABASES, {})
    def get_selected_databases(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.database.Database]
        """
        databases = {}
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `db_server_id`, `dom_id`, `name`, `type`
                FROM `data_bases`
                WHERE `dom_id` IN ({domain_ids})
            """.format(domain_ids=format_ids_list(domain_ids)))
            for row in rows:
                databases[row['id']] = Database(
                    database_id=row['id'], database_server_id=row['db_server_id'], domain_id=row['dom_id'],
                    name=row['name'], database_server_type=row['type'],
                )
        return databases

    @cached
    @data_dump(messages.DATA_DUMP_DATABASES_USERS, {})
    def get_selected_database_users(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.database_user.DatabaseUser]
        """
        database_users = {}
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `account_id`, `db_id`, `dom_id`, `db_server_id`, `login`
                FROM `db_users`
                WHERE `dom_id` IN ({domain_ids})
            """.format(domain_ids=format_ids_list(domain_ids)))
            for row in rows:
                database_users[row['id']] = DatabaseUser(
                    database_user_id=row['id'], account_id=row['account_id'], database_id=row['db_id'],
                    domain_id=row['dom_id'], database_server_id=row['db_server_id'], login=row['login'],
                    role=None,
                )
        return database_users

    @cached
    @data_dump(messages.DATA_DUMP_SCHEDULED_TASKS, {})
    def get_selected_scheduled_tasks(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.scheduled_task.ScheduledTask]
        """
        return {}

    @cached
    @data_dump(messages.DATA_DUMP_SSL_CERTIFICATES, {})
    def get_selected_repositories(self):
        """
        :rtype: dict[str|unicode, list[str|unicode]]
        """
        selected_repositories_ids = [
            domain.cert_rep_id
            for domain in self.get_selected_domains().values() if domain.cert_rep_id != '0'
        ]
        repositories = defaultdict(list)
        for repositories_ids in self._iter_ranges(selected_repositories_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `rep_id`, `component_id`
                FROM `Repository`
                WHERE `rep_id` IN ({repositories_ids})
            """.format(repositories_ids=format_ids_list(repositories_ids)))
            for row in rows:
                repositories[row['rep_id']].append(row['component_id'])
        return repositories

    @cached
    @data_dump(messages.DATA_DUMP_SSL_CERTIFICATES, {})
    def get_selected_certificates(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.certificate.Certificate]
        """
        selected_certificates_ids = set()
        for domain in self.get_selected_domains().values():
            for certificate_id in self.get_selected_repositories().get(domain.cert_rep_id, []):
                selected_certificates_ids.add(certificate_id)
        certificates = {}
        for certificates_ids in self._iter_ranges(selected_certificates_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `name`, `cert`, `pvt_key`, `csr`, `ca_cert`
                FROM `certificates`
                WHERE `id` IN ({certificates_ids})
            """.format(certificates_ids=format_ids_list(certificates_ids)), log_output=False)
            for row in rows:
                certificates[row['id']] = Certificate(
                    certificate_id=row['id'], name=row['name'], cert=row['cert'],
                    pvt_key=row['pvt_key'], csr=row['csr'], ca_cert=row['ca_cert'],
                )
        return certificates

    @cached
    def get_selected_applications(self):
        """
        :rtype: xml.etree.ElementTree.Element
        """
        try:
            with self._dump_agent.source_server().runner() as runner:
                assert isinstance(runner, BaseRunner)
                aps_backup_name = "ApsBackup-12.0.phpe"
                remote_aps_backup_path = self._dump_agent.source_server().get_session_file_path(aps_backup_name)
                runner.upload_file(
                    migrator_utils.get_package_extras_file_path(parallels.plesk.source.plesk, aps_backup_name),
                    remote_aps_backup_path
                )
                remote_webspaces_ids_list_path = self._dump_agent.source_server().get_session_file_path('webspaces-ids.list')
                runner.upload_file_content(
                    remote_webspaces_ids_list_path,
                    "\n".join([domain.domain_id for domain in self.get_domains(webspace_id='0')])
                )
                dump_xml = runner.execute_command(
                    '{php} -dauto_prepend_file="" {aps_backup} --get-instances -webspaces-ids-from {webspaces_ids}',
                    dict(
                        php=os.path.join(self._dump_agent.source_server().plesk_dir, 'admin', 'bin', 'php'),
                        aps_backup=remote_aps_backup_path,
                        webspaces_ids=remote_webspaces_ids_list_path,
                    )
                ).stdout
                return ElementTree.fromstring(dump_xml)
        except Exception as e:
            logger.exception()
            self._dump_agent.global_context.pre_check_report.add_issue(
                'dump_data',
                Issue.SEVERITY_WARNING,
                safe_format(messages.FAILED_TO_MAKE_APS_APPLICATIONS_DUMP, error=e)
            )
        return ElementTree.Element('applications')

    @cached
    @data_dump(messages.DATA_DUMP_PROTECTED_DIRECTORIES, {})
    def get_selected_protected_directories(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.protected_directory.ProtectedDirectory]
        """
        protected_directories = {}
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `realm`, `path`, `dom_id`
                FROM `protected_dirs`
                WHERE `dom_id` IN ({domain_ids})
            """.format(domain_ids=format_ids_list(domain_ids)))
            for row in rows:
                protected_directories[row['id']] = ProtectedDirectory(
                    protected_directory_id=row['id'], domain_id=row['dom_id'], path=row['path'], realm=row['realm'],
                )
        return protected_directories

    @cached
    @data_dump(messages.DATA_DUMP_PROTECTED_DIRECTORIES_USERS, {})
    def get_selected_protected_directories_users(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.protected_directory_user.ProtectedDirectoryUser]
        """
        protected_directories_users = {}
        for protected_directories_ids in self._iter_ranges(self.get_selected_protected_directories().keys()):
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `login`, `account_id`, `pd_id`
                FROM `pd_users`
                WHERE `pd_id` IN ({protected_directories_ids})
            """.format(protected_directories_ids=format_ids_list(protected_directories_ids)))
            for row in rows:
                protected_directories_users[row['id']] = ProtectedDirectoryUser(
                    protected_directory_user_id=row['id'], protected_directory_id=row['pd_id'],
                    account_id=row['account_id'], login=row['login'],
                )
        return protected_directories_users

    @cached
    @data_dump(messages.DATA_DUMP_ANONYMOUS_FTP, {})
    def get_selected_anonymous_ftp(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.anonymous_ftp.AnonymousFtp]
        """
        anonymous_ftp = {}
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `dom_id`, `max_conn`, `bandwidth`, `incoming`, `incoming_readable`, `incoming_subdirs`,
                    `status`, `quota`, `display_login`, `login_text`
                FROM `anon_ftp`
                WHERE `dom_id` IN ({domain_ids})
            """.format(domain_ids=format_ids_list(domain_ids)))
            for row in rows:
                anonymous_ftp[row['dom_id']] = AnonymousFtp(
                    domain_id=row['dom_id'], max_conn=row['max_conn'], bandwidth=row['bandwidth'],
                    incoming=row['incoming'], incoming_readable=row['incoming_readable'],
                    incoming_subdirs=row['incoming_subdirs'], status=row['status'], quota=row['quota'],
                    display_login=row['display_login'], login_text=row['login_text'],
                )
        return anonymous_ftp

    @cached
    @data_dump(messages.DATA_DUMP_HOTLINK_PROTECTION, {})
    def get_selected_hotlink_protection(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.hotlink_protection.HotlinkProtection]
        """
        hotlink_protection = {}
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `dom_id`, `enabled`, `extensions`
                FROM `hotlink_prot`
                WHERE `dom_id` IN ({domain_ids})
            """.format(domain_ids=format_ids_list(domain_ids)))
            for row in rows:
                hotlink_protection[row['dom_id']] = HotlinkProtection(
                    domain_id=row['dom_id'], enabled=row['enabled'], extensions=row['extensions'],
                )
        return hotlink_protection

    @cached
    @data_dump(messages.DATA_DUMP_HOTLINK_PROTECTION, {})
    def get_selected_hotlink_friends(self):
        """
        :rtype: dict[str|unicode, list[str|unicode]]
        """
        hotlink_friends = defaultdict(list)
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `dom_id`, `friend_dom`
                FROM `hotlink_friends`
                WHERE `dom_id` IN ({domain_ids})
            """.format(domain_ids=format_ids_list(domain_ids)))
            for row in rows:
                hotlink_friends[row['dom_id']].append(row['friend_dom'])
        return hotlink_friends

    @cached
    @data_dump(messages.DATA_DUMP_DYNAMIC_IP_SECURITY, {})
    def get_selected_dynamic_ip_security(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.dynamic_ip_security.DynamicIpSecurity]
        """
        dynamic_ip_security = {}
        for domain_ids in self._iter_ranges(self._get_selected_domain_ids()):
            rows = self._dump_agent.execute_sql("""
                SELECT `dom_id`, `isDenyByConcurrentRequests`, `maxConcurrentRequests`,
                    `isDenyByRequestRate`, `maxRequests`, `requestInterval`
                FROM `DynamicIpSecurity`
                WHERE `dom_id` IN ({domain_ids})
            """.format(domain_ids=format_ids_list(domain_ids)))
            for row in rows:
                dynamic_ip_security[row['dom_id']] = DynamicIpSecurity(
                    domain_id=row['dom_id'], is_deny_by_concurrent_requests=row['isDenyByConcurrentRequests'],
                    max_concurrent_requests=row['maxConcurrentRequests'],
                    is_deny_by_request_rate=row['isDenyByRequestRate'], max_requests=row['maxRequests'],
                    request_interval=row['requestInterval'],
                )
        return dynamic_ip_security

    @cached
    @data_dump(messages.DATA_DUMP_WEB_SERVER_SETTINGS, {})
    def get_selected_web_server_settings(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        selected_web_server_settings_ids = []
        for template_items in self.get_selected_templates_items().values():
            web_server_settings_id = template_items.get('webServerSettingsId')
            if web_server_settings_id is not None and web_server_settings_id.isdigit():
                selected_web_server_settings_ids.append(web_server_settings_id)
        for domain_id in self._get_selected_domain_ids():
            web_server_settings_id = self.get_domain_parameter(domain_id, 'webServerSettingsId')
            if web_server_settings_id is not None and web_server_settings_id.isdigit():
                selected_web_server_settings_ids.append(web_server_settings_id)
        web_server_settings = defaultdict(dict)
        for web_server_settings_ids in self._iter_ranges(selected_web_server_settings_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `webServerSettingsId`, `name`, `value`
                FROM `WebServerSettingsParameters`
                WHERE `webServerSettingsId` IN ({web_server_settings_ids})
            """.format(web_server_settings_ids=format_ids_list(web_server_settings_ids)))
            for row in rows:
                web_server_settings[row['webServerSettingsId']][row['name']] = row['value']
        return web_server_settings

    @cached
    @data_dump(messages.DATA_DUMP_SPAM_FILTERING_SETTINGS, {})
    def get_selected_spam_filters_preferences(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, list[str|unicode]]]
        """
        selected_spam_filters_ids = []
        for mail_name in self.get_selected_mail_names().values():
            domain = self.get_selected_domains().get(mail_name.domain_id)
            if not domain:
                continue
            spam_filter = self.get_all_spam_filters().get('%s@%s' % (mail_name.name, domain.ascii_name))
            if spam_filter:
                selected_spam_filters_ids.append(spam_filter.spam_filter_id)
        spam_filters_preferences = defaultdict(lambda: defaultdict(list))
        for spam_filters_ids in self._iter_ranges(selected_spam_filters_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `spamfilter_id`, `preference`, `value`
                FROM `spamfilter_preferences`
                WHERE `spamfilter_id` IN ({spam_filters_ids})
            """.format(spam_filters_ids=format_ids_list(spam_filters_ids)))
            for row in rows:
                spam_filters_preferences[row['spamfilter_id']][row['preference']].append(row['value'])
        return spam_filters_preferences

    @cached
    @data_dump(messages.DATA_DUMP_SPAM_FILTERING_SETTINGS, {})
    def get_selected_sa_parameters(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, list[str|unicode]]]
        """
        selected_sa_mail_names_ids = []
        for mail_name in self.get_selected_mail_names().values():
            domain = self.get_selected_domains().get(mail_name.domain_id)
            if not domain:
                continue
            sa_mail_name = self.get_all_sa_mail_names().get('%s@%s' % (mail_name.name, domain.ascii_name))
            if sa_mail_name:
                selected_sa_mail_names_ids.append(sa_mail_name.mail_name_id)
        sa_parameters = defaultdict(lambda: defaultdict(list))
        for sa_mail_names_ids in self._iter_ranges(selected_sa_mail_names_ids):
            rows = self._dump_agent.execute_sql("""
                SELECT `conf_id`, `name`, `value`
                FROM `sa_param`
                WHERE `conf_id` IN ({sa_mail_names_ids})
            """.format(sa_mail_names_ids=format_ids_list(sa_mail_names_ids)))
            for row in rows:
                sa_parameters[row['conf_id']][row['name']].append(row['value'])
        return sa_parameters

    @cached
    def get_all_mail_names(self, user_id=None):
        """
        :type user_id: str | unicode | None
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_name.MailName]
        """
        return {
            mail_name.mail_name_id: mail_name
            for mail_name in self._get_all_mail_names().values()
            if (not user_id or mail_name.user_id == user_id)
        }

    @cached
    @data_dump(messages.DATA_DUMP_DATABASES, {})
    def get_all_database_servers(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.database_server.DatabaseServer]
        """
        database_servers = {}
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `type`, `host`, `port`, `server_version`, `admin_login`, `admin_password`
            FROM `DatabaseServers`
            WHERE `last_error` <> 'credentials_not_set'
        """, log_output=False)
        for row in rows:
            admin_password, admin_password_type = None, None

            is_linux_database_with_psa = (
                not self._dump_agent.source_server().is_windows() and
                row['host'] == 'localhost' and
                str(row['port']) == '3306' and
                row['type'] == 'mysql'
            )

            if is_linux_database_with_psa:
                try:
                    with self._dump_agent.source_server().runner() as runner:
                        admin_password = runner.get_file_contents('/etc/psa/.psa.shadow')
                        admin_password_type = 'plain'
                except Exception as e:
                    logger.exception()
                    self._dump_agent.global_context.pre_check_report.add_issue(
                        'dump_data',
                        Issue.SEVERITY_WARNING,
                        safe_format(messages.FAILED_TO_GET_PANEL_DATABASE_PASSWORD, error=e)
                    )
            elif self._dump_agent.source_server().is_windows() or row['admin_password'].startswith('$AES-'):
                admin_password, admin_password_type = row['admin_password'], 'sym'
            else:
                admin_password, admin_password_type = row['admin_password'], 'plain'

            database_servers[row['id']] = DatabaseServer(
                database_server_id=row['id'], server_type=row['type'], host=row['host'], port=row['port'],
                version=row['server_version'], admin_login=row['admin_login'], admin_password=admin_password,
                admin_password_type=admin_password_type,
            )
        encrypted_passwords = [
            EncryptedPasswordItem(
                id_object=database_server.database_server_id,
                description=safe_format(
                    'database server with id={id}',
                    id=database_server.database_server_id
                ),
                value=database_server.admin_password
            ) for database_server in database_servers.values() if database_server.admin_password_type == 'sym'
        ]

        source_server = self._dump_agent.source_server()
        password_decryptor = PleskDecryptor(source_server)
        decrypted_password_items, decrypted_password_errors = password_decryptor.decrypt_data(encrypted_passwords)

        for decrypted_password_item in decrypted_password_items:
            database_server_id = decrypted_password_item.id_object
            database_server = database_servers.get(database_server_id)
            if database_server:
                properties = database_server.as_dictionary()
                properties['admin_password'] = decrypted_password_item.value
                properties['admin_password_type'] = 'plain'
                database_servers[database_server_id] = DatabaseServer(**properties)

        for decrypted_password_error in decrypted_password_errors:
            database_server_id = decrypted_password_error.id_object
            database_server = database_servers[database_server_id]

            logger.fwarn(
                messages.FAILED_TO_DECRYPT_DATABASE_SERVER_PASSWORD,
                database_server_type=db_type_pretty_name(database_server.server_type),
                database_server_host=database_server.host,
                error=decrypted_password_error.error_message
            )

            self._dump_agent.global_context.pre_check_report.add_issue(
                'dump_data',
                Issue.SEVERITY_WARNING,
                safe_format(
                    messages.FAILED_TO_DECRYPT_DATABASE_SERVER_PASSWORD,
                    database_server_type=db_type_pretty_name(database_server.server_type),
                    database_server_host=database_server.host,
                    error=decrypted_password_error.error_message
                )
            )

        return database_servers

    @cached
    @data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_USERS_ROLES, {})
    def get_all_general_permissions(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.general_permission.GeneralPermission]
        """
        permissions = {}
        rows = self._dump_agent.execute_sql("SELECT `id`, `code` FROM `smb_generalPermissions`")
        for row in rows:
            permissions[row['id']] = GeneralPermission(general_permission_id=row['id'], code=row['code'])
        return permissions

    @cached
    @data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_USERS_ROLES, {})
    def get_all_service_providers(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.service_provider.ServiceProvider]
        """
        providers = {}
        rows = self._dump_agent.execute_sql("SELECT `id`, `classname` FROM `smb_serviceProviders`")
        for row in rows:
            providers[row['id']] = ServiceProvider(service_provider_id=row['id'], classname=row['classname'])
        return providers

    @cached
    @data_dump(messages.DATA_DUMP_SPAM_FILTERING_SETTINGS, {})
    def get_all_sa_mail_names(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.sa_mail_name.SpamAssassinMailName]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `mailname`, `flt_enabled`, `rw_subject_tag`, `hits_required`, `spam_action`
            FROM `sa_conf`
        """)
        return {
            row['mailname']: SpamAssassinMailName(
                mail_name_id=row['id'], name=row['mailname'], flt_enabled=row['flt_enabled'],
                rw_subject_tag=row['rw_subject_tag'], hits_required=row['hits_required'],
                spam_action=row['spam_action'],
            )
            for row in rows
        }

    @cached
    @data_dump(messages.DATA_DUMP_SPAM_FILTERING_SETTINGS, {})
    def get_all_sa_lists(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, list[str|unicode]]]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `mailname`, `pattern`, `color`
            FROM `sa_list`
        """)
        sa_lists = defaultdict(lambda: defaultdict(list))
        for row in rows:
            sa_lists[row['mailname']][row['color']].append(row['pattern'])
        return sa_lists

    @cached
    @data_dump(messages.DATA_DUMP_SPAM_FILTERING_SETTINGS, {})
    def get_all_spam_filters(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.spam_filter.SpamFilter]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `username`
            FROM `spamfilter`
        """)
        return {
            row['username']: SpamFilter(
                spam_filter_id=row['id'], username=row['username'],
            )
            for row in rows
        }

    def is_server_component_installed(self, service_node_id, component_name):
        """
        :type service_node_id: str|unicode
        :type component_name: str|unicode
        :rtype: bool
        """
        return bool(self.get_server_components(service_node_id).get(component_name))

    def is_dns_enabled(self, service_node_id):
        """
        :type service_node_id: str|unicode
        :rtype: bool
        """
        if self._dump_agent.source_server().is_windows():
            dns_server_node = self._get_all_packages().find("type[@name='dnsserver']")
            if dns_server_node and dns_server_node.get('default'):
                return dns_server_node.get('default') != 'none'
            return False
        else:
            return self.is_server_component_installed(service_node_id, 'bind')

    @cached
    def _get_admin_id(self):
        """
        :rtype: str|unicode
        :raises: parallels.core.MigrationError
        """
        rows = self._dump_agent.execute_sql("SELECT `id` FROM `clients` WHERE `type` = 'admin'")
        for row in rows:
            return row['id']
        raise MigrationError(messages.UNABLE_TO_FIND_ADMIN_USER)

    @cached
    @data_dump(messages.DATA_DUMP_MAILBOXES, {})
    def _get_all_mail_names(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_name.MailName]
        """
        mail_names = {}
        if self._dump_agent.source_server().is_windows():
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `userId`, `account_id`, `dom_id`, `mail_name`, `postbox`, `mail_group`,
                  `autoresponder`, `mbox_quota`, `description`
                FROM `mail`
            """)
            for row in rows:
                mail_names[row['id']] = MailName(
                    mail_name_id=row['id'], user_id=row['userId'], account_id=row['account_id'],
                    domain_id=row['dom_id'], name=row['mail_name'], postbox=row['postbox'],
                    mail_group=row['mail_group'], auto_responder=row['autoresponder'],
                    mbox_quota=row['mbox_quota'], description=row['description'],
                    spamfilter=None, virusfilter=None,
                )
        else:
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `userId`, `account_id`, `dom_id`, `mail_name`, `postbox`, `mail_group`,
                    `autoresponder`, `mbox_quota`, `description`, `spamfilter`, `virusfilter`
                FROM `mail`
            """)
            for row in rows:
                mail_names[row['id']] = MailName(
                    mail_name_id=row['id'], user_id=row['userId'], account_id=row['account_id'],
                    domain_id=row['dom_id'], name=row['mail_name'], postbox=row['postbox'],
                    mail_group=row['mail_group'], auto_responder=row['autoresponder'],
                    mbox_quota=row['mbox_quota'], description=row['description'],
                    spamfilter=row['spamfilter'], virusfilter=row['virusfilter'],
                )
        return mail_names

    @cached
    @data_dump(messages.DATA_DUMP_CLIENTS, set())
    def _get_selected_clients_ids(self):
        """
        :rtype: set[str|unicode]
        """
        admin_id = self._get_admin_id()
        _, domains_owners_ids = self._get_selected_domains_and_owners_ids()
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `parent_id`, `login`
            FROM `clients`
        """)
        clients_ids = set()
        for row in rows:
            if (
                self._selection.is_reseller_selected(row['login']) or
                self._selection.is_client_selected(row['login']) or
                row['id'] in domains_owners_ids
            ):
                clients_ids.add(row['id'])
                if row['parent_id'] not in [None, '0', admin_id]:
                    clients_ids.add(row['parent_id'])
        return clients_ids

    @cached
    def _get_selected_domain_ids(self):
        """
        :rtype: set[str|unicode]
        """
        domains_ids, _ = self._get_selected_domains_and_owners_ids()
        return domains_ids

    @cached
    @data_dump(messages.DATA_DUMP_DOMAINS, (set(), set()))
    def _get_selected_domains_and_owners_ids(self):
        """
        :rtype: tuple[set[str|unicode], set[str|unicode]]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `webspace_id`, `cl_id`, `displayName`
            FROM `domains` ORDER BY `webspace_id`
        """)
        domains_ids = set()
        owners_ids = set()
        for row in rows:
            if row['webspace_id'] == '0':
                if self._selection.is_domain_selected(row['displayName']):
                    domains_ids.add(row['id'])
                    owners_ids.add(row['cl_id'])
            else:
                if row['webspace_id'] in domains_ids:
                    domains_ids.add(row['id'])
        return domains_ids, owners_ids

    def _get_clients_by_id(self, clients_ids):
        """
        :type clients_ids: list[str|unicode]
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.hosting_description.plesk_entity.client.Client]
        """
        clients = {}
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `parent_id`, `account_id`, `pool_id`, `limits_id`, `perm_id`, `login`, `type`, `guid`,
                `external_id`, `locale`, `cr_date`, `status`, `pname`, `email`, `cname`, `country`, `pcode`, `state`,
                `city`, `address`, `phone`, `fax`, `description`
            FROM `clients`
            WHERE `id` IN ({clients_ids})
        """.format(clients_ids=format_ids_list(clients_ids)))
        for row in rows:
            clients[row['id']] = Client(
                client_id=row['id'], owner_id=row['parent_id'], account_id=row['account_id'],
                ip_pool_id=row['pool_id'], limits_id=row['limits_id'], permissions_id=row['perm_id'],
                login=row['login'], client_type=row['type'], guid=row['guid'], external_id=row['external_id'],
                locale=row['locale'], creation_date=row['cr_date'], status=row['status'], name=row['pname'],
                email=row['email'], company=row['cname'], country=row['country'], postal_code=row['pcode'],
                state=row['state'], city=row['city'], address=row['address'], phone=row['phone'], fax=row['fax'],
                description=row['description'],
            )
        return clients

    def _iter_ranges(self, entities_ids):
        """
        :type entities_ids: list[str|unicode] | set[str|unicode]
        :rtype: collections.Iterable[list[str|unicode]]
        """
        entities_ids_range = []
        for entity_id in entities_ids:
            entities_ids_range.append(entity_id)
            if len(entities_ids_range) < self.MAX_ENTITIES_PER_QUERY:
                continue
            yield entities_ids_range
            entities_ids_range = []
        if entities_ids_range:
            yield entities_ids_range

    @cached
    @data_dump(messages.DATA_DUMP_SERVER_COMPONENTS, ElementTree.Element('packages'))
    def _get_all_packages(self):
        """
        :rtype: xml.etree.ElementTree.Element
        """
        with self._dump_agent.source_server().runner() as runner:
            assert isinstance(runner, BaseRunner)
            packages_xml = runner.execute_command(
                '{defpackagemng} --get',
                dict(defpackagemng=ntpath.join(self._dump_agent.source_server().plesk_dir, r'admin\bin\defpackagemng')),
            ).stdout
            return ElementTree.fromstring(packages_xml)


def format_ids_list(ids):
    """
    :type ids: list[str|unicode]
    :rtype: str|unicode
    """
    for item in ids:
        if not item.isdigit():
            raise MigrationError(messages.INVALID_INT_VALUE)
    return ','.join(ids)
