def _refresh_token(self):
        """
        Called internally to Authenticate to generate a new refreshed token as the existing one has expired
        """

        token_refresh_payload = {
            "Accept": "application/json",
            "grant_type": "password",
            "client_id": self.client_key,
            "username": self.username,
            "password": self.password,
            "refresh_token": self.token.refresh_token
        }

        # Attempt to get a token
        token_refresh_result = requests.post(self.cache.get_uri("Token"),
                                             data=token_refresh_payload,
                                             verify=self.https_verify)

        # Get the token details
        if token_refresh_result.status_code == 200:
            # We were successful  - create a new token using the newly obtained json token
            self.token = AccessToken()
            self.token.load(token_refresh_result.json())
        else:
            # We could not refresh our a token - this is bad, generate an exception
            raise Exception(
                "Error Refreshing Token, returned HTTP:{} '{}' - {}".format(
                    token_refresh_result.status_code,
                    token_refresh_result.reason, token_refresh_result.text))
    def _get_token(self):
        """
        Called internally to Authenticate to Cherwell and retrieves a new Bearer token
        """

        token_payload = {
            "Accept": "application/json",
            "grant_type": "password",
            "client_id": self.client_key,
            "username": self.username,
            "password": self.password
        }

        # Attempt to get a token
        token_result = requests.post(self.cache.get_uri("Token"),
                                     data=token_payload,
                                     verify=self.https_verify)

        if token_result.status_code == 200:
            # We were successful  - create a new token using the newly obtained json token
            self.token = AccessToken()
            self.token.load(token_result.json())
        else:
            # We could not get a token - this is bad, generate an exception
            raise Exception(
                "Error Getting Token, returned HTTP:{} '{}' - {}".format(
                    token_result.status_code, token_result.reason,
                    token_result.text))
class Connection:
    """
    The 'Connection' class serves to provide a REST API Connection to a Cherwell instance.
    All interactions to a Cherwell instance occur through an instantiation of this class.

    Parameters
    ----------

    uri : str

        This is the base uri for the Cherwell instance and typically take the form of 'http://127.0.0.1' or
        'https://127.0.0.1'. The IP address can be replaced by the host name provided DNS name resolution is setup
        correctly'

    client_key : str

        This is the REST API key generated by the Cherwell Administration tool (Orange Pill). This is needed to
        properly authenticate with the Cherwell REST API

    username : str

        The username of a Cherwell user that has access to all the objects and functions necessary for the
        required interaction

    password : str

        The password of the user referred to in the 'username' :parameter

    cache: ObjectCache

        An instance of the ObjectCache class that was previously created and/or saved/cached. If a ObjectCache instance
        is not provided then this is defaulted to 'None' and a new ObjectCache will be instantiated

    token : AccessToken

        An instance of the AccessToken class that was previously created or saved/cached. If a AccessToken is not
        provided then this is defaulted to 'None' and a new AccessToken will be created during the first
        interaction to the Cherwell REST API

    https_verify : bool

        Flag set to verify SSL Certificates during requests API Calls

    """
    def __init__(self,
                 base_uri="",
                 client_key="",
                 username="",
                 password="",
                 cache=None,
                 token=None,
                 https_verify=False):
        """ Connection instance initialisation """

        # Set the values we need that were passed in
        self.uri = base_uri
        self.username = username
        if not password or not client_key:
            try:
                self.client_key = CherwellCredentials.decrypt_message(
                    "cherwell_api_key")
                self.password = CherwellCredentials.decrypt_message(
                    "cherwell_password")
            except:
                raise Exception(
                    "Unable to locate encrypted Cherwell API Key and Password. Please run the function CherwellCrentials.create_encrypted_cherwell_credentials(password, client_key) first or pass in the password and client key in the CherwellClient.Connection Method"
                )
        else:
            self.client_key = client_key
            self.password = password

        # Initialise a cache object - we can reuse
        if isinstance(cache, ObjectCache):

            # we have been passed a saved cache object
            self.cache = cache
        else:
            # We don't have a saved cache object - instantiate a new one
            self.cache = ObjectCache(self.uri, self.client_key)

        if token is not None and isinstance(token, AccessToken):

            # We have been passed an existing token
            self.token = token
        else:
            # Token not correct type - start with no token
            self.token = None

        self.https_verify = https_verify

    def cache(self):
        """
        Returns the ObjectCache object used by the Connection to avoid repeated round trip to through the Cherwell API
        for data that is largely reused
        """

        return self.cache

    def token(self):
        """
        Returns the current AccessToken which contains the Bearer token used for repeated API
        calls without the need to re-authenticate
        """

        return self.token

    def _get_token(self):
        """
        Called internally to Authenticate to Cherwell and retrieves a new Bearer token
        """

        token_payload = {
            "Accept": "application/json",
            "grant_type": "password",
            "client_id": self.client_key,
            "username": self.username,
            "password": self.password
        }

        # Attempt to get a token
        token_result = requests.post(self.cache.get_uri("Token"),
                                     data=token_payload,
                                     verify=self.https_verify)

        if token_result.status_code == 200:
            # We were successful  - create a new token using the newly obtained json token
            self.token = AccessToken()
            self.token.load(token_result.json())
        else:
            # We could not get a token - this is bad, generate an exception
            raise Exception(
                "Error Getting Token, returned HTTP:{} '{}' - {}".format(
                    token_result.status_code, token_result.reason,
                    token_result.text))

    def _refresh_token(self):
        """
        Called internally to Authenticate to generate a new refreshed token as the existing one has expired
        """

        token_refresh_payload = {
            "Accept": "application/json",
            "grant_type": "password",
            "client_id": self.client_key,
            "username": self.username,
            "password": self.password,
            "refresh_token": self.token.refresh_token
        }

        # Attempt to get a token
        token_refresh_result = requests.post(self.cache.get_uri("Token"),
                                             data=token_refresh_payload,
                                             verify=self.https_verify)

        # Get the token details
        if token_refresh_result.status_code == 200:
            # We were successful  - create a new token using the newly obtained json token
            self.token = AccessToken()
            self.token.load(token_refresh_result.json())
        else:
            # We could not refresh our a token - this is bad, generate an exception
            raise Exception(
                "Error Refreshing Token, returned HTTP:{} '{}' - {}".format(
                    token_refresh_result.status_code,
                    token_refresh_result.reason, token_refresh_result.text))

    def _get_authorisation_header(self):
        """ Called internally to create the Authorisation header for the REST API Calls. This method will
        request new tokens if needed or alternatively attempt to refresh an existing token"""

        if self.token is None or self.token.access_token is None:
            # Get a new token
            self._get_token()
        elif self.token.expired() or self.token.access_token is None:
            # Refresh the token if its expired
            self._refresh_token()

        if self.token:
            # We are connected
            return {
                "Authorization": "Bearer " + self.token.access_token,
                "Content-Type": "application/json"
            }
        else:
            # we are not connected
            return None

    def get_business_object_id(self, business_object_name, refresh=False):
        """
        This method used the Cherwell API to retrieve the unique identifier associated with the
        requested Cherwell business object.

        If the id is found in cache then it is returned from cache and a call to the
        Cherwell REST API is avoided.

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the id is being requested

        refresh : bool

            A value of true indicates that an API call must be made to get the business object id again, even
            if the id is already available in the cache

        Returns
        -------

            The Cherwell business object id if found or 'None' if not found

        """

        if not self.cache.get_business_object_id(
                business_object_name) or refresh:

            # Default the return to None
            self.cache.set_business_object_id(business_object_name, None)

            # Call the API to get the result
            result_business_object_id = requests.get(
                self.cache.get_uri("BusinessObjectID") + business_object_name,
                headers=self._get_authorisation_header(),
                verify=self.https_verify)

            if result_business_object_id.status_code == 200:

                if result_business_object_id.text != "[]":
                    # Success - save the value
                    self.cache.set_business_object_id(
                        business_object_name,
                        result_business_object_id.json()[0]["busObId"])
            else:

                # There was a problem with the API call, generate an exception
                raise Exception(
                    "Error getting the business object id for '{}'. HTTP:{} '{}' - {}"
                    .format(business_object_name,
                            result_business_object_id.status_code,
                            result_business_object_id.reason,
                            result_business_object_id.text))

        return self.cache.get_business_object_id(business_object_name)

    def get_business_object_template(self,
                                     business_object_name,
                                     refresh=False):
        """
        This method used the Cherwell API to retrieve a json template (list of fields and attributes)
        for a given business object. These templates can be used to get the value of fields but can
        also be used for updating an object in Cherwell.

        If the template is found in cache then it is returned from cache and a call to the
        Cherwell REST API is avoided.

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the template is being requested.

        refresh : bool

            A value of True indicates that an API call must be made to get the business object template again, even
            if the template is already available in the cache.

        Returns
        -------

            The Cherwell business object template if found or 'None' if not found.

        """

        if not self.cache.get_business_object_template(
                business_object_name) or refresh:

            # Default the return to None
            self.cache.set_business_object_template(business_object_name, None)

            # Get the object id for the requested object
            object_id = self.get_business_object_id(business_object_name)

            # Setup the templateRequest
            template_request = {
                "busObID": object_id,
                "includeRequired": "True",
                "includeAll": "True"
            }

            # Call the API to get the template
            result_business_object_template = requests.post(
                self.cache.get_uri("BusinessObjectTemplate"),
                json=template_request,
                headers=self._get_authorisation_header(),
                verify=self.https_verify)

            if result_business_object_template.status_code == 200:

                # Success - we have the template
                self.cache.set_business_object_template(
                    business_object_name,
                    result_business_object_template.json())

        return self.cache.get_business_object_template(business_object_name)

    def get_business_object_field_id(self,
                                     business_object_name,
                                     field_name,
                                     refresh=False):
        """
        This method used the Cherwell API to retrieve the field id for a passed in Cherwell
        business object and field name. The field id is retrieved from a business object template.

        The Business object Template generally provides the following information about a Cherwell Business Object.

            -- The list of fields configured in the business object as well as the following attributes for each field:
                -- displayName
                -- name
                -- value
                -- html
                -- dirty (signifies a change of value if true)
                -- fieldId

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the field is being requested.

        field_name : str

            The 'internalname' of a field in the passed in Cherwell business object,
            for which the id is being requested.

        refresh : bool

            A value of True indicates that an API call must be made to get the business object template again, even
            if the template is already available in the cache.

        Returns
        -------

            The Cherwell field id of the requested field, in the requested Cherwell business object.

        """

        # Default the returning Id to None
        field_id = None

        # Make sure we have the template
        template = self.get_business_object_template(business_object_name,
                                                     refresh)

        # Loop through the template and find the field name
        for field in template["fields"]:

            if field["name"] == field_name:
                # We matched the field - get the id
                field_id = field["fieldId"]

                # Break out of loop - we found what we need
                break

        return field_id

    def get_business_object_summary(self, business_object_name, refresh=False):
        """
        This method used the Cherwell API to retrieve a Cherwell business object summary.

        The Business object Summary generally provides the following information about a Cherwell Business Object.

            -- What type of object it is i.e. 'Major', 'Supporting', 'Lookup' or 'Group'
            -- The field id used to main the state
            -- The display name
            -- The internal name
            -- The business object id
            -- The configured lifecycle states such as 'Active', 'Inactive', 'Retired' as examples
            -- The first record id in the business object table
            -- The fieldid for the field used as the Record Id (RecId)

        If the summary is found in cache then it is returned from cache and a call to the
        Cherwell REST API is avoided.

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the summary is being requested.

        refresh : bool

            A value of True indicates that an API call must be made to get the business object summary again, even
            if the template is already available in the cache.

        Returns
        -------

            The Cherwell summary for the requested business object or 'None' if not found.

        """

        if not self.cache.get_business_object_summary(
                business_object_name) or refresh:

            # Default the return to None
            self.cache.set_business_object_summary(business_object_name, None)

            # Call the API to get the summary
            result_business_object_summary = requests.get(
                "{}{}".format(self.cache.get_uri("BusinessObjectSummary"),
                              business_object_name),
                headers=self._get_authorisation_header(),
                verify=self.https_verify)

            if result_business_object_summary.status_code == 200:

                if result_business_object_summary.text != "[]":
                    # Success - we have the summary
                    self.cache.set_business_object_summary(
                        business_object_name,
                        result_business_object_summary.json())

        return self.cache.get_business_object_summary(business_object_name)

    def get_business_object_internalname(self,
                                         business_object_id,
                                         refresh=False):
        """
        This method uses the Cherwell API to retrieve a Cherwell business object schema, which it then uses
        to get the business objects internal name.

        The Business schema generally provides the following information about a Cherwell Business Object.

            -- Details about every field on the business object
            -- The internal name of the business object

        If the schema is found in cache then it is returned from cache and a call to the
        Cherwell REST API is avoided.

        Parameters
        ----------

        business_object_id : str

            The Cherwell business object id of an object for which the schema is being requested.

        refresh : bool

            A value of True indicates that an API call must be made to get the business object schema again, even
            if the schema is already available in the cache.

        Returns
        -------

            The Cherwell Business Object Internal name for the requested business object or 'None' if not found.

        """

        if not self.cache.get_business_object_schema(
                business_object_id) or refresh:

            # Default the return to None
            self.cache.set_business_object_schema(business_object_id, None)

            # Call the API to get the schema
            result_business_object_schema = requests.get(
                "{}{}".format(self.cache.get_uri("BusinessObjectSchema"),
                              business_object_id),
                headers=self._get_authorisation_header(),
                verify=self.https_verify)

            if result_business_object_schema.status_code == 200:

                if result_business_object_schema.text != "[]":
                    # Success - we have the schema
                    self.cache.set_business_object_schema(
                        business_object_id,
                        result_business_object_schema.json())

        if self.cache.get_business_object_schema(business_object_id):
            return self.cache.get_business_object_schema(
                business_object_id)["name"]
        else:
            return self.cache.get_business_object_schema(business_object_id)

    def set_business_object_field_value_in_template(self, template, field_name,
                                                    value):
        """
        This method can be used to update a fields value in a Cherwell business object template. The template can then
        be passed back to Cherwell to be used to create or update a Business Object.

        If the field is found, and the value updated - then the 'dirty' flag is also set to True. This lets
        Cherwell know the value has been updated - when we pass the template back.

        Parameters
        ----------

        template : str

            The Cherwell business object template to update. The template was likely previously obtained using the
            'get_business_object_template' method.

        field_name : str

            The name of the field to search for in the template and whose value needs to be updated.

        value : str

            The value to set the required field to, in the Cherwell business object template.

        Returns
        -------

            'True' if the field was found and updated, else false.

        """

        # Default the search to false
        field_found = False

        # Loop through the passed in template and modify the fields to the required values
        for field_id in range(0, len(template["fields"])):
            if template["fields"][field_id]["displayName"] == field_name:

                # We matched the field - set the value
                template["fields"][field_id]["value"] = value

                # Set the dirty flag to true to show it changed
                template["fields"][field_id]["dirty"] = True

                # Set found to true
                field_found = True

        # Caller can decide what to do with False returns (field not found)
        return field_found

    def create_business_object(self, template, business_object_name):
        """
        This method can be used to create a new business object using a Cherwell business object template.

        Parameters
        ----------

        template : str

            The Cherwell business object template to use to create the new business object.

        business_object_name : str

            The name of the Cherwell business object to create using the passed in template.

        Returns : tuple
        -------

            -- New record public id
            -- New record id
            -- business object id

            Generates an exception if the new business object cannot be created

        """

        # Get the object id for the requested object
        business_object_id = self.get_business_object_id(business_object_name)

        # Setup the payload
        payload = {"busObID": business_object_id, "fields": template["fields"]}

        # Attempt to save the new Business Object
        result_new = requests.post(self.cache.get_uri("BusinessObjectSave"),
                                   json=payload,
                                   headers=self._get_authorisation_header(),
                                   verify=self.https_verify)

        if result_new.status_code == 200:
            # Success - return the public id and record id
            return str(result_new.json()["busObPublicId"]), str(
                result_new.json()["busObRecId"]), business_object_id
        else:
            # There was a problem with the API call, generate an exception
            raise Exception(
                "Error creating the new business object '{}'. HTTP:{} '{}' - {}"
                .format(business_object_name, result_new.status_code,
                        result_new.reason, result_new.text))

    def get_new_business_object(self, business_object_name, refresh=False):
        """
        This method can be used to return a obtain a new 'BusinessObject' instance with all the
        fields set as object instance properties. This 'Business Object' instance can be used to update properties
        and 'Save()' a new record to Cherwell.

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the the 'BusinessObject' is being requested.

        refresh : bool

            A value of True indicates that an API call must be made to get the business object properties again,
            even if they have previously been retrieved. This can be used if its expected the number/name of
            fields has changed since the last call.

        Returns : BusinessObject
        -------

        A new 'BusinessObject' instance with instance properties that reflect the fields of the
        Cherwell Business Object. These properties can be modified and the 'Save()' method can be called to save a new
        record.

        """

        # Default the returning object to None
        new_business_object = None

        # Get the object id for the requested object
        business_object_id = self.get_business_object_id(business_object_name)

        # Get the business object template
        self.get_business_object_template(business_object_name, refresh)

        # Create a new business object passing in the name and object id, and the
        # function used to get a new header
        new_business_object = BusinessObject(
            business_object_name, self._get_authorisation_header,
            self.cache.get_uri("BusinessObjectSave"),
            self.cache.get_uri("BusinessObjectDelete"), business_object_id)

        # Load the fields from a passed in template
        new_business_object.load(
            self.cache.get_business_object_template(business_object_name))

        # return the new business object
        return new_business_object

    def get_business_objects(self, filter_object):
        """
        This method can be used to return a list of 'BusinessObject' instances that meet the search criteria
         defined in the passed in SimpleFilter. These 'Business Object' instances can be used to update properties
        and 'Save()' the values back to Cherwell. These 'Business Object' instances even have a 'Delete()' method
        to delete the record in Cherwell.

        SimpleFilter example:
        --------------------

            # Create a new instance of a SimpleFilter object - setting the business object name to look for
            search_filter = Filter.SimpleFilter("CustomerInternal")

            # add a search filter where you are looking for all customers with 'FirstName' that contains 'Susan'
            search_filter.add_search_fields("FirstName", "contains", "Susan")

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the the 'BusinessObject' is being requested.

        filter_object : SimpleFilter

            An instance of the SimpleFilter class that has been populated with the search fields. By default
            SimpleFilter are setup to return all fields so all attributes can be populated

        Returns : Tuple(Int: Num Records, List: BusinessObjects)
        -------

            -- The number of business objects returned
            -- A list of business objects with fully populated fields as instance properties.

            These properties can be modified and the 'Save()' method can be called to save a new record.
            The Business Object can also be deleted by calling the 'Delete()' method

        """
        # Return value
        business_objects = []

        # setup the filter array
        filter_info = []

        # return value to indicate how many rows found
        rows = 0

        # Start at page number 0
        page_number = 1

        if isinstance(filter_object, Filter.SimpleFilter):

            # Get the object id for the requested object
            business_object_id = self.get_business_object_id(
                filter_object.business_object_name)

            # Loop through each search field in the filter
            for field in filter_object.search_fields:
                # Get the field id for the field
                field_id = self.get_business_object_field_id(
                    filter_object.business_object_name, field["Field"])

                # Setup the filter to be passed in
                filter_info.append({
                    "fieldID": field_id,
                    "operator": field["Operator"],
                    "value": field["Value"]
                })

        else:
            # Raise an exception
            raise ValueError(
                "Filter object is not an instance of SimpleFilter")

        while True:
            # Setup the payload
            payload = {
                "busObID": business_object_id,
                "filters": filter_info,
                "includeAllFields": filter_object.include_all_fields,
                "pageNumber": page_number
            }

            # Attempt to get the required business object
            result_bus_obj_search = requests.post(
                self.cache.get_uri("BusinessSearchResults"),
                json=payload,
                headers=self._get_authorisation_header(),
                verify=self.https_verify)

            if result_bus_obj_search.status_code == 200:

                search_results = result_bus_obj_search.json()

                # Get how many rows were returned
                rows = search_results["totalRows"]

                # If we got no records back - means we have found them all - exit
                if len(search_results["businessObjects"]) == 0:
                    break

                # Loop through all the business object returned and get a new business object for each
                for business_object in search_results["businessObjects"]:

                    # Create a new business object passing in the name and object id, and the
                    # function used to get a new header
                    new_business_object = BusinessObject(
                        filter_object.business_object_name,
                        self._get_authorisation_header,
                        self.cache.get_uri("BusinessObjectSave"),
                        self.cache.get_uri("BusinessObjectDelete"),
                        business_object_id, business_object["busObRecId"],
                        business_object["busObPublicId"])

                    # Load the fields from the obtained template
                    new_business_object.load(business_object)

                    # add the new object to the business objects collection
                    business_objects.append(new_business_object)

            else:
                # There was a problem with the API call, generate an exception
                raise Exception(
                    "Error searching for business objects '{}'. HTTP:{} '{}' - {}"
                    .format(filter_object.business_object_name,
                            result_bus_obj_search.status_code,
                            result_bus_obj_search.reason,
                            result_bus_obj_search.text))

            # Increment the page number
            page_number = page_number + 1

        # Return the business object
        return rows, business_objects

    def get_business_records(self, filter_object):
        """
        This method can be used to retrieve the business object record id 'busObRecId' as well as any number of fields
        from matching business object in Cherwell.

        AdHocFilter example:
        --------------------

            # Create a new instance of a AdHocFilter object - setting the business object name to look for
            search_filter = Filter.AdHocFilter("CustomerInternal")

            # add a search filter where you are looking for all customers with 'FirstName' that contains 'Susan'
            search_filter.add_search_fields("FirstName", "contains", "Susan")

            # Specify you want 2 fields returned
            search_filter.add_fields("Email")
            search_filter.add_fields("Phone")

        Tips:

            -- To return all fields, set 'include_all_fields' to 'True' when initialising the AdHocFilter.
            -- Operators for searching can include:
                -- 'eq' for equal to
                -- 'gt' for greater than
                -- 'lt' for less than
                -- 'contains' for a like comparison to see if the field contains that value
                -- 'startswith' to check if the fields starts with the supplied value

        Parameters
        ----------

        filter_object : AdHocFilter

            An instance of the AdhocFilter class that has been populated with the search fields and fields to return.

        Returns : List
        -------

            A list of matching records and their fields will be returned in a list/dictionary structure.

            Example:

            [
                {
                    'busObRecId': u'9365db49c71a885a905d8b4a9d82e19eccaf3aba16',
                    'fields':
                    [
                        {'fieldId': u'9337c23403da50548767c24e48aede27e5dd274521', 'displayName': u'Email',
                        'name': u'Email', 'value': u'*****@*****.**'},
                        {'fieldId': u'9337c23376e3ad38cdfa00495c92f33775a61c393b', 'displayName': u'Phone',
                        'name': u'Phone', 'value': u'(719) 386.7000'}
                    ]
                },
                {
                    'busObRecId': u'93d5c17090fa0a4981c57b459eb051c83f5475409b',
                    'fields':
                    [
                        {'fieldId': u'9337c23403da50548767c24e48aede27e5dd274521', 'displayName': u'Email',
                        'name': u'Email', 'value': u"Susan.O'*****@*****.**"},
                        {'fieldId': u'9337c23376e3ad38cdfa00495c92f33775a61c393b', 'displayName': u'Phone',
                        'name': u'Phone','value': u'(719) 386.7000'}
                    ]
                }
            ]

        """

        # Return value
        business_objects = []

        # setup the filter array
        filter_info = []

        # Setup the fields array
        field_list = []

        # return value to indicate how many rows found
        rows = 0

        if isinstance(filter_object, Filter.AdHocFilter):

            # Get the object id for the requested object
            business_object_id = self.get_business_object_id(
                filter_object.business_object_name)

            # Loop through each search field in the filter
            for field in filter_object.search_fields:
                # Get the field id for the field
                field_id = self.get_business_object_field_id(
                    filter_object.business_object_name, field["Field"])

                # Setup the filter to be passed in
                filter_info.append({
                    "fieldID": field_id,
                    "operator": field["Operator"],
                    "value": field["Value"]
                })

            # Loop though each field in the filter
            for field in filter_object.fields:
                # Get the field id for each field
                field_id = self.get_business_object_field_id(
                    filter_object.business_object_name, field)

                # Append the field id to the fields list
                field_list.append(field_id)

        else:
            # Raise an exception
            raise ValueError("Filter object is not a AdHocFilter")

        # Setup the payload
        payload = {
            "busObID": business_object_id,
            "fields": field_list,
            "filters": filter_info,
            "includeAllFields": filter_object.include_all_fields
        }

        # Attempt to get the required business object
        result_search = requests.post(
            self.cache.get_uri("BusinessSearchResults"),
            json=payload,
            headers=self._get_authorisation_header(),
            verify=self.https_verify)

        if result_search.status_code == 200:

            # We have some records
            search_results = result_search.json()

            # Get how many rows were returned
            rows = search_results["totalRows"]

            # Loop through all the business object returned
            for business_object in search_results["businessObjects"]:

                field_result_list = []

                # Loop through each field returned
                for field in business_object["fields"]:

                    # Add to the field result list
                    field_dict = {
                        "name": field["name"],
                        "value": field["value"],
                        "displayName": field["displayName"],
                        "fieldId": field["fieldId"]
                    }
                    field_result_list.append(field_dict)

                # Add the fields and business object id into a dictionary
                business_object_dict = {
                    "busObRecId": business_object["busObRecId"],
                    "fields": field_result_list
                }

                # Add the this dictionary to the array to be returned
                business_objects.append(business_object_dict)
        else:
            # There was a problem with the API call, generate an exception
            raise Exception(
                "Error searching for business objects '{}'. HTTP:{} '{}' - {}".
                format(filter_object.business_object_name,
                       result_search.status_code, result_search.reason,
                       result_search.text))

        # Return the number of object and the array of objects
        return rows, business_objects

    def get_saved_search_results(self,
                                 association,
                                 scope,
                                 search_name,
                                 scope_owner="None",
                                 include_schema="false",
                                 results_as_simple_results_list="false"):
        """
        This method can be used to return the paged results of a saved search.

        Parameters
        ----------

        association : str

            The Business Object Display Name this search belongs to
        
        scope : str

            The scope the search belongs to e.g. Global
        
        scope_owner : str

            The scope owner the search belongs to defaulted to None
        
        search_name : str

            The name of the saved search you want to run
        
        include_schema : str

            Use to include the table schema of the saved search. If false, results contain the fieldid 
            and field value without field information. Default is false. true or false
        
        results_as_simple_results_list : str

            Indicates if the results should be returned in a simple results list format or a table format.
            Default is a table format. true or false

        Returns : List
        -------

            A list of paged results of a saved search.

            Example:

            {
                "businessObjects": [
                    {
                        "busObId": "944ee68cc77c109557cf534f1d815ddca5282a0316",
                        "busObPublicId": "SL1-Dev",
                        "busObRecId": "94633cf94d6d924d17bc87477aae77d684a553b8f3",
                        "fields": [
                            {
                            "dirty": false,
                            "displayName": "Federation Name",
                            "fieldId": "BO:944ee68cc77c109557cf534f1d815ddca5282a0316,FI:944ee692875572fd7d5ddf44ddb1a2b66f93fbed3e",
                            "html": null,
                            "name": "FederationName",
                            "value": "SL1-Dev"
                            },
                            {
                            "dirty": false,
                            "displayName": "Federation Type",
                            "fieldId": "BO:944ee68cc77c109557cf534f1d815ddca5282a0316,FI:944eef6fda969659714fed4498adabf5a712f72ada",
                            "html": null,
                            "name": "FederationType",
                            "value": "ScienceLogic"
                            },
                            {
                            "dirty": false,
                            "displayName": "Status",
                            "fieldId": "BO:944ee68cc77c109557cf534f1d815ddca5282a0316,FI:944ee695474b8573c238464f298f8aaeb380ff511e",
                            "html": null,
                            "name": "Status",
                            "value": "Active"
                            }
                        ],
                        "links": [
                            {
                            "name": "Delete Record",
                            "url": "http://ec2-3-104-173-24.ap-southeast-2.compute.amazonaws.com/CherwellAPI/api/V1/deletebusinessobject/busobid/944ee68cc77c109557cf534f1d815ddca5282a0316/busobrecid/94633cf94d6d924d17bc87477aae77d684a553b8f3"
                            }
                        ],
                        "errorCode": null,
                        "errorMessage": null,
                        "hasError": false
                        }
                    ],
                    "hasPrompts": false,
                    "links": [
                        {
                        "name": "First Page",
                        "url": "http://ec2-3-104-173-24.ap-southeast-2.compute.amazonaws.com/CherwellAPI/api/V1/getsearchresults/association/944ee68cc77c109557cf534f1d815ddca5282a0316/scope/Global/scopeowner/None/searchname/All%20Active%20Federation%20Sources?pagesize=200&pagenumber=1"
                        },
                        {
                        "name": "Last Page",
                        "url": "http://ec2-3-104-173-24.ap-southeast-2.compute.amazonaws.com/CherwellAPI/api/V1/getsearchresults/association/944ee68cc77c109557cf534f1d815ddca5282a0316/scope/Global/scopeowner/None/searchname/All%20Active%20Federation%20Sources?pagesize=200&pagenumber=1"
                        },
                        {
                        "name": "Next Page",
                        "url": "http://ec2-3-104-173-24.ap-southeast-2.compute.amazonaws.com/CherwellAPI/api/V1/getsearchresults/association/944ee68cc77c109557cf534f1d815ddca5282a0316/scope/Global/scopeowner/None/searchname/All%20Active%20Federation%20Sources?pagesize=200&pagenumber=1"
                        },
                        {
                        "name": "Previous Page",
                        "url": "http://ec2-3-104-173-24.ap-southeast-2.compute.amazonaws.com/CherwellAPI/api/V1/getsearchresults/association/944ee68cc77c109557cf534f1d815ddca5282a0316/scope/Global/scopeowner/None/searchname/All%20Active%20Federation%20Sources?pagesize=200&pagenumber=1"
                        }
                    ],
                    "prompts": [],
                    "searchResultsFields": [],
                    "simpleResults": null,
                    "totalRows": 1,
                    "errorCode": null,
                    "errorMessage": null,
                    "hasError": false
                }

        """

        # Return value
        business_objects = []

        # setup the filter array
        filter_info = []

        # Setup the fields array
        field_list = []

        # return value to indicate how many rows found
        rows = 0

        # Get the object id for the requested object
        business_object_id = self.get_business_object_id(association)

        saved_search_uri = self.cache.get_uri("RunSavedSearch").replace(
            "[association]",
            business_object_id).replace("[scope]", scope).replace(
                "[scopeowner]",
                scope_owner).replace("[searchname]", search_name).replace(
                    "[includeschema]",
                    include_schema).replace("[resultsAsSimpleResultsList]",
                                            results_as_simple_results_list)

        # Attempt to get the required business object
        result_search = requests.get(saved_search_uri,
                                     headers=self._get_authorisation_header(),
                                     verify=self.https_verify)

        if result_search.status_code == 200:

            # We have some records
            search_results = result_search.json()

            # Get how many rows were returned
            rows = search_results["totalRows"]

            # Loop through all the business object returned
            for business_object in search_results["businessObjects"]:

                field_result_list = []

                # Loop through each field returned
                for field in business_object["fields"]:

                    # Add to the field result list
                    field_dict = {
                        "name": field["name"],
                        "value": field["value"],
                        "displayName": field["displayName"],
                        "fieldId": field["fieldId"]
                    }
                    field_result_list.append(field_dict)

                # Add the fields and business object id into a dictionary
                business_object_dict = {
                    "busObRecId": business_object["busObRecId"],
                    "fields": field_result_list
                }

                # Add the this dictionary to the array to be returned
                business_objects.append(business_object_dict)
        else:
            # There was a problem with the API call, generate an exception
            raise Exception(
                "Error searching for business objects '{}'. HTTP:{} '{}' - {}".
                format(association, result_search.status_code,
                       result_search.reason, result_search.text))

        # Return the number of object and the array of objects
        return rows, business_objects

    def logout(self):
        """
        This method can be used to logout of the Cherwell instance. The user referenced in the authentication token is
        instantly logged out. A new CherwellClient instance and token will be required to login again.
        """

        # Attempt to logout
        response = requests.delete(self.cache.get_uri("Logout"),
                                   headers=self._get_authorisation_header(),
                                   verify=self.https_verify)

        if response.status_code == 200:
            # Success - Logged out successfully
            return str(response.status_code)
        else:
            # There was a problem with the API call, generate an exception
            raise Exception(
                "Error logging out of Cherwell 'HTTP:{} '{}' - {}".format(
                    response.status_code, response.reason, response.text))
Exemplo n.º 4
0
class Connection:
    """
    The 'Connection' class serves to provide a REST API Connection to a Cherwell instance.
    All interactions to a Cherwell instance occur through an instantiation of this class.

    Parameters
    ----------

    uri : str

        This is the base uri for the Cherwell instance and typically take the form of 'http://127.0.0.1' or
        'https://127.0.0.1'. The IP address can be replaced by the host name provided DNS name resolution is setup
        correctly'

    client_key : str

        This is the REST API key generated by the Cherwell Administration tool (Orange Pill). This is needed to
        properly authenticate with the Cherwell REST API

    username : str

        The username of a Cherwell user that has access to all the objects and functions necessary for the
        required interaction

    password : str

        The password of the user referred to in the 'username' :parameter

    cache: ObjectCache

        An instance of the ObjectCache class that was previously created and/or saved/cached. If a ObjectCache instance
        is not provided then this is defaulted to 'None' and a new ObjectCache will be instantiated

    token : AccessToken

        An instance of the AccessToken class that was previously created or saved/cached. If a AccessToken is not
        provided then this is defaulted to 'None' and a new AccessToken will be created during the first
        interaction to the Cherwell REST API

    """
    def __init__(self,
                 base_uri="",
                 client_key="",
                 username="",
                 password="",
                 cache=None,
                 token=None):
        """ Connection instance initialisation """

        # Set the values we need that were passed in
        self.uri = base_uri
        self.client_key = client_key
        self.username = username
        self.password = password

        # Initialise a cache object - we can reuse
        if isinstance(cache, ObjectCache):

            # we have been passed a saved cache object
            self.cache = cache
        else:
            # We don't have a saved cache object - instantiate a new one
            self.cache = ObjectCache(self.uri, self.client_key)

        if token is not None and isinstance(token, AccessToken):

            # We have been passed an existing token
            self.token = token
        else:
            # Token not correct type - start with no token
            self.token = None

    def cache(self):
        """
        Returns the ObjectCache object used by the Connection to avoid repeated round trip to through the Cherwell API
        for data that is largely reused
        """

        return self.cache

    def token(self):
        """
        Returns the current AccessToken which contains the Bearer token used for repeated API
        calls without the need to re-authenticate
        """

        return self.token

    def _get_token(self):
        """
        Called internally to Authenticate to Cherwell and retrieves a new Bearer token
        """

        token_payload = {
            "Accept": "application/json",
            "grant_type": "password",
            "client_id": self.client_key,
            "username": self.username,
            "password": self.password
        }

        # Attempt to get a token
        token_result = requests.post(self.cache.get_uri("Token"),
                                     data=token_payload)

        if token_result.status_code == 200:
            # We were successful  - create a new token using the newly obtained json token
            self.token = AccessToken()
            self.token.load(token_result.json())
        else:
            # We could not get a token - this is bad, generate an exception
            raise Exception(
                "Error Getting Token, returned HTTP:{} '{}' - {}".format(
                    token_result.status_code, token_result.reason,
                    token_result.text))

    def _refresh_token(self):
        """
        Called internally to Authenticate to generate a new refreshed token as the existing one has expired
        """

        token_refresh_payload = {
            "Accept": "application/json",
            "grant_type": "password",
            "client_id": self.client_key,
            "username": self.username,
            "password": self.password,
            "refresh_token": self.token.refresh_token
        }

        # Attempt to get a token
        token_refresh_result = requests.post(self.cache.get_uri("Token"),
                                             data=token_refresh_payload)

        # Get the token details
        if token_refresh_result.status_code == 200:
            # We were successful  - create a new token using the newly obtained json token
            self.token = AccessToken()
            self.token.load(token_refresh_result.json())
        else:
            # We could not refresh our a token - this is bad, generate an exception
            raise Exception(
                "Error Refreshing Token, returned HTTP:{} '{}' - {}".format(
                    token_refresh_result.status_code,
                    token_refresh_result.reason, token_refresh_result.text))

    def _get_authorisation_header(self):
        """ Called internally to create the Authorisation header for the REST API Calls. This method will
        request new tokens if needed or alternatively attempt to refresh an existing token"""

        if self.token is None or self.token.access_token is None:
            # Get a new token
            self._get_token()
        elif self.token.expired() or self.token.access_token is None:
            # Refresh the token if its expired
            self._refresh_token()

        if self.token:
            # We are connected
            return {
                "Authorization": "Bearer " + self.token.access_token,
                "Content-Type": "application/json"
            }
        else:
            # we are not connected
            return None

    def get_business_object_id(self, business_object_name, refresh=False):
        """
        This method used the Cherwell API to retrieve the unique identifier associated with the
        requested Cherwell business object.

        If the id is found in cache then it is returned from cache and a call to the
        Cherwell REST API is avoided.

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the id is being requested

        refresh : bool

            A value of true indicates that an API call must be made to get the business object id again, even
            if the id is already available in the cache

        Returns
        -------

            The Cherwell business object id if found or 'None' if not found

        """

        if not self.cache.get_business_object_id(
                business_object_name) or refresh:

            # Default the return to None
            self.cache.set_business_object_id(business_object_name, None)

            # Call the API to get the result
            result_business_object_id = requests.get(
                self.cache.get_uri("BusinessObjectID") + business_object_name,
                headers=self._get_authorisation_header())

            if result_business_object_id.status_code == 200:

                if result_business_object_id.text != "[]":
                    # Success - save the value
                    self.cache.set_business_object_id(
                        business_object_name,
                        result_business_object_id.json()[0]["busObId"])
            else:

                # There was a problem with the API call, generate an exception
                raise Exception(
                    "Error getting the business object id for '{}'. HTTP:{} '{}' - {}"
                    .format(business_object_name,
                            result_business_object_id.status_code,
                            result_business_object_id.reason,
                            result_business_object_id.text))

        return self.cache.get_business_object_id(business_object_name)

    def get_business_object_template(self,
                                     business_object_name,
                                     refresh=False):
        """
        This method used the Cherwell API to retrieve a json template (list of fields and attributes)
        for a given business object. These templates can be used to get the value of fields but can
        also be used for updating an object in Cherwell.

        If the template is found in cache then it is returned from cache and a call to the
        Cherwell REST API is avoided.

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the template is being requested.

        refresh : bool

            A value of True indicates that an API call must be made to get the business object template again, even
            if the template is already available in the cache.

        Returns
        -------

            The Cherwell business object template if found or 'None' if not found.

        """

        if not self.cache.get_business_object_template(
                business_object_name) or refresh:

            # Default the return to None
            self.cache.set_business_object_template(business_object_name, None)

            # Get the object id for the requested object
            object_id = self.get_business_object_id(business_object_name)

            # Setup the templateRequest
            template_request = {
                "busObID": object_id,
                "includeRequired": "True",
                "includeAll": "True"
            }

            # Call the API to get the template
            result_business_object_template = requests.post(
                self.cache.get_uri("BusinessObjectTemplate"),
                json=template_request,
                headers=self._get_authorisation_header())

            if result_business_object_template.status_code == 200:

                # Success - we have the template
                self.cache.set_business_object_template(
                    business_object_name,
                    result_business_object_template.json())

        return self.cache.get_business_object_template(business_object_name)

    def get_business_object_field_id(self,
                                     business_object_name,
                                     field_name,
                                     refresh=False):
        """
        This method used the Cherwell API to retrieve the field id for a passed in Cherwell
        business object and field name. The field id is retrieved from a business object template.

        The Business object Template generally provides the following information about a Cherwell Business Object.

            -- The list of fields configured in the business object as well as the following attributes for each field:
                -- displayName
                -- name
                -- value
                -- html
                -- dirty (signifies a change of value if true)
                -- fieldId

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the field is being requested.

        field_name : str

            The 'internalname' of a field in the passed in Cherwell business object,
            for which the id is being requested.

        refresh : bool

            A value of True indicates that an API call must be made to get the business object template again, even
            if the template is already available in the cache.

        Returns
        -------

            The Cherwell field id of the requested field, in the requested Cherwell business object.

        """

        # Default the returning Id to None
        field_id = None

        # Make sure we have the template
        template = self.get_business_object_template(business_object_name,
                                                     refresh)

        # Loop through the template and find the field name
        for field in template["fields"]:

            if field["name"] == field_name:
                # We matched the field - get the id
                field_id = field["fieldId"]

                # Break out of loop - we found what we need
                break

        return field_id

    def get_business_object_summary(self, business_object_name, refresh=False):
        """
        This method used the Cherwell API to retrieve a Cherwell business object summary.

        The Business object Summary generally provides the following information about a Cherwell Business Object.

            -- What type of object it is i.e. 'Major', 'Supporting', 'Lookup' or 'Group'
            -- The field id used to main the state
            -- The display name
            -- The internal name
            -- The business object id
            -- The configured lifecycle states such as 'Active', 'Inactive', 'Retired' as examples
            -- The first record id in the business object table
            -- The fieldid for the field used as the Record Id (RecId)

        If the summary is found in cache then it is returned from cache and a call to the
        Cherwell REST API is avoided.

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the summary is being requested.

        refresh : bool

            A value of True indicates that an API call must be made to get the business object summary again, even
            if the template is already available in the cache.

        Returns
        -------

            The Cherwell summary for the requested business object or 'None' if not found.

        """

        if not self.cache.get_business_object_summary(
                business_object_name) or refresh:

            # Default the return to None
            self.cache.set_business_object_summary(business_object_name, None)

            # Call the API to get the summary
            result_business_object_summary = requests.get(
                "{}{}".format(self.cache.get_uri("BusinessObjectSummary"),
                              business_object_name),
                headers=self._get_authorisation_header())

            if result_business_object_summary.status_code == 200:

                if result_business_object_summary.text != "[]":
                    # Success - we have the summary
                    self.cache.set_business_object_summary(
                        business_object_name,
                        result_business_object_summary.json())

        return self.cache.get_business_object_summary(business_object_name)

    def set_business_object_field_value_in_template(self, template, field_name,
                                                    value):
        """
        This method can be used to update a fields value in a Cherwell business object template. The template can then
        be passed back to Cherwell to be used to create or update a Business Object.

        If the field is found, and the value updated - then the 'dirty' flag is also set to True. This lets
        Cherwell know the value has been updated - when we pass the template back.

        Parameters
        ----------

        template : str

            The Cherwell business object template to update. The template was likely previously obtained using the
            'get_business_object_template' method.

        field_name : str

            The name of the field to search for in the template and whose value needs to be updated.

        value : str

            The value to set the required field to, in the Cherwell business object template.

        Returns
        -------

            'True' if the field was found and updated, else false.

        """

        # Default the search to false
        field_found = False

        # Loop through the passed in template and modify the fields to the required values
        for field_id in range(0, len(template["fields"])):
            if template["fields"][field_id]["displayName"] == field_name:

                # We matched the field - set the value
                template["fields"][field_id]["value"] = value

                # Set the dirty flag to true to show it changed
                template["fields"][field_id]["dirty"] = True

                # Set found to true
                field_found = True

        # Caller can decide what to do with False returns (field not found)
        return field_found

    def create_business_object(self, template, business_object_name):
        """
        This method can be used to create a new business object using a Cherwell business object template.

        Parameters
        ----------

        template : str

            The Cherwell business object template to use to create the new business object.

        business_object_name : str

            The name of the Cherwell business object to create using the passed in template.

        Returns : tuple
        -------

            -- New record public id
            -- New record id
            -- business object id

            Generates an exception if the new business object cannot be created

        """

        # Get the object id for the requested object
        business_object_id = self.get_business_object_id(business_object_name)

        # Setup the payload
        payload = {"busObID": business_object_id, "fields": template["fields"]}

        # Attempt to save the new Business Object
        result_new = requests.post(self.cache.get_uri("BusinessObjectSave"),
                                   json=payload,
                                   headers=self._get_authorisation_header())

        if result_new.status_code == 200:
            # Success - return the public id and record id
            return str(result_new.json()["busObPublicId"]), str(
                result_new.json()["busObRecId"]), business_object_id
        else:
            # There was a problem with the API call, generate an exception
            raise Exception(
                "Error creating the new business object '{}'. HTTP:{} '{}' - {}"
                .format(business_object_name, result_new.status_code,
                        result_new.reason, result_new.text))

    def get_new_business_object(self, business_object_name, refresh=False):
        """
        This method can be used to return a obtain a new 'BusinessObject' instance with all the
        fields set as object instance properties. This 'Business Object' instance can be used to update properties
        and 'Save()' a new record to Cherwell.

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the the 'BusinessObject' is being requested.

        refresh : bool

            A value of True indicates that an API call must be made to get the business object properties again,
            even if they have previously been retrieved. This can be used if its expected the number/name of
            fields has changed since the last call.

        Returns : BusinessObject
        -------

        A new 'BusinessObject' instance with instance properties that reflect the fields of the
        Cherwell Business Object. These properties can be modified and the 'Save()' method can be called to save a new
        record.

        """

        # Default the returning object to None
        new_business_object = None

        # Get the object id for the requested object
        business_object_id = self.get_business_object_id(business_object_name)

        # Get the business object template
        self.get_business_object_template(business_object_name, refresh)

        # Create a new business object passing in the name and object id, and the
        # function used to get a new header
        new_business_object = BusinessObject(
            business_object_name, self._get_authorisation_header,
            self.cache.get_uri("BusinessObjectSave"),
            self.cache.get_uri("BusinessObjectDelete"), business_object_id)

        # Load the fields from a passed in template
        new_business_object.load(
            self.cache.get_business_object_template(business_object_name))

        # return the new business object
        return new_business_object

    def get_business_objects(self, filter_object):
        """
        This method can be used to return a list of 'BusinessObject' instances that meet the search criteria
         defined in the passed in SimpleFilter. These 'Business Object' instances can be used to update properties
        and 'Save()' the values back to Cherwell. These 'Business Object' instances even have a 'Delete()' method
        to delete the record in Cherwell.

        SimpleFilter example:
        --------------------

            # Create a new instance of a SimpleFilter object - setting the business object name to look for
            search_filter = Filter.SimpleFilter("CustomerInternal")

            # add a search filter where you are looking for all customers with 'FirstName' that contains 'Susan'
            search_filter.add_search_fields("FirstName", "contains", "Susan")

        Parameters
        ----------

        business_object_name : str

            The Cherwell 'internalname' of an object for which the the 'BusinessObject' is being requested.

        filter_object : SimpleFilter

            An instance of the SimpleFilter class that has been populated with the search fields. By default
            SimpleFilter are setup to return all fields so all attributes can be populated

        Returns : Tuple(Int: Num Records, List: BusinessObjects)
        -------

            -- The number of business objects returned
            -- A list of business objects with fully populated fields as instance properties.

            These properties can be modified and the 'Save()' method can be called to save a new record.
            The Business Object can also be deleted by calling the 'Delete()' method

        """
        # Return value
        business_objects = []

        # setup the filter array
        filter_info = []

        # return value to indicate how many rows found
        rows = 0

        if isinstance(filter_object, Filter.SimpleFilter):

            # Get the object id for the requested object
            business_object_id = self.get_business_object_id(
                filter_object.business_object_name)

            # Loop through each search field in the filter
            for field in filter_object.search_fields:
                # Get the field id for the field
                field_id = self.get_business_object_field_id(
                    filter_object.business_object_name, field["Field"])

                # Setup the filter to be passed in
                filter_info.append({
                    "fieldID": field_id,
                    "operator": field["Operator"],
                    "value": field["Value"]
                })

        else:
            # Raise an exception
            raise ValueError(
                "Filter object is not an instance of SimpleFilter")

        # Setup the payload
        payload = {
            "busObID": business_object_id,
            "filters": filter_info,
            "includeAllFields": filter_object.include_all_fields
        }

        # Attempt to get the required business object
        result_bus_obj_search = requests.post(
            self.cache.get_uri("BusinessSearchResults"),
            json=payload,
            headers=self._get_authorisation_header())

        if result_bus_obj_search.status_code == 200:

            search_results = result_bus_obj_search.json()

            # Get how many rows were returned
            rows = search_results["totalRows"]

            # Loop through all the business object returned and get a new business object for each
            for business_object in search_results["businessObjects"]:

                # Create a new business object passing in the name and object id, and the
                # function used to get a new header
                new_business_object = BusinessObject(
                    filter_object.business_object_name,
                    self._get_authorisation_header,
                    self.cache.get_uri("BusinessObjectSave"),
                    self.cache.get_uri("BusinessObjectDelete"),
                    business_object_id, business_object["busObRecId"],
                    business_object["busObPublicId"])

                # Load the fields from the obtained template
                new_business_object.load(business_object)

                # add the new object to the business objects collection
                business_objects.append(new_business_object)

        else:
            # There was a problem with the API call, generate an exception
            raise Exception(
                "Error searching for business objects '{}'. HTTP:{} '{}' - {}".
                format(filter_object.business_object_name,
                       result_bus_obj_search.status_code,
                       result_bus_obj_search.reason,
                       result_bus_obj_search.text))

        # Return the business object
        return rows, business_objects

    def get_business_records(self, filter_object):
        """
        This method can be used to retrieve the business object record id 'busObRecId' as well as any number of fields
        from matching business object in Cherwell.

        AdHocFilter example:
        --------------------

            # Create a new instance of a AdHocFilter object - setting the business object name to look for
            search_filter = Filter.AdHocFilter("CustomerInternal")

            # add a search filter where you are looking for all customers with 'FirstName' that contains 'Susan'
            search_filter.add_search_fields("FirstName", "contains", "Susan")

            # Specify you want 2 fields returned
            search_filter.add_fields("Email")
            search_filter.add_fields("Phone")

        Tips:

            -- To return all fields, set 'include_all_fields' to 'True' when initialising the AdHocFilter.
            -- Operators for searching can include:
                -- 'eq' for equal to
                -- 'gt' for greater than
                -- 'lt' for less than
                -- 'contains' for a like comparison to see if the field contains that value
                -- 'startswith' to check if the fields starts with the supplied value

        Parameters
        ----------

        filter_object : AdHocFilter

            An instance of the AdhocFilter class that has been populated with the search fields and fields to return.

        Returns : List
        -------

            A list of matching records and their fields will be returned in a list/dictionary structure.

            Example:

            [
                {
                    'busObRecId': u'9365db49c71a885a905d8b4a9d82e19eccaf3aba16',
                    'fields':
                    [
                        {'fieldId': u'9337c23403da50548767c24e48aede27e5dd274521', 'displayName': u'Email',
                        'name': u'Email', 'value': u'*****@*****.**'},
                        {'fieldId': u'9337c23376e3ad38cdfa00495c92f33775a61c393b', 'displayName': u'Phone',
                        'name': u'Phone', 'value': u'(719) 386.7000'}
                    ]
                },
                {
                    'busObRecId': u'93d5c17090fa0a4981c57b459eb051c83f5475409b',
                    'fields':
                    [
                        {'fieldId': u'9337c23403da50548767c24e48aede27e5dd274521', 'displayName': u'Email',
                        'name': u'Email', 'value': u"Susan.O'*****@*****.**"},
                        {'fieldId': u'9337c23376e3ad38cdfa00495c92f33775a61c393b', 'displayName': u'Phone',
                        'name': u'Phone','value': u'(719) 386.7000'}
                    ]
                }
            ]

        """

        # Return value
        business_objects = []

        # setup the filter array
        filter_info = []

        # Setup the fields array
        field_list = []

        # return value to indicate how many rows found
        rows = 0

        if isinstance(filter_object, Filter.AdHocFilter):

            # Get the object id for the requested object
            business_object_id = self.get_business_object_id(
                filter_object.business_object_name)

            # Loop through each search field in the filter
            for field in filter_object.search_fields:
                # Get the field id for the field
                field_id = self.get_business_object_field_id(
                    filter_object.business_object_name, field["Field"])

                # Setup the filter to be passed in
                filter_info.append({
                    "fieldID": field_id,
                    "operator": field["Operator"],
                    "value": field["Value"]
                })

            # Loop though each field in the filter
            for field in filter_object.fields:
                # Get the field id for each field
                field_id = self.get_business_object_field_id(
                    filter_object.business_object_name, field)

                # Append the field id to the fields list
                field_list.append(field_id)

        else:
            # Raise an exception
            raise ValueError("Filter object is not a AdHocFilter")

        # Setup the payload
        payload = {
            "busObID": business_object_id,
            "fields": field_list,
            "filters": filter_info,
            "includeAllFields": filter_object.include_all_fields
        }

        # Attempt to get the required business object
        result_search = requests.post(
            self.cache.get_uri("BusinessSearchResults"),
            json=payload,
            headers=self._get_authorisation_header())

        if result_search.status_code == 200:

            # We have some records
            search_results = result_search.json()

            # Get how many rows were returned
            rows = search_results["totalRows"]

            # Loop through all the business object returned
            for business_object in search_results["businessObjects"]:

                field_result_list = []

                # Loop through each field returned
                for field in business_object["fields"]:

                    # Add to the field result list
                    field_dict = {
                        "name": field["name"],
                        "value": field["value"],
                        "displayName": field["displayName"],
                        "fieldId": field["fieldId"]
                    }
                    field_result_list.append(field_dict)

                # Add the fields and business object id into a dictionary
                business_object_dict = {
                    "busObRecId": business_object["busObRecId"],
                    "fields": field_result_list
                }

                # Add the this dictionary to the array to be returned
                business_objects.append(business_object_dict)
        else:
            # There was a problem with the API call, generate an exception
            raise Exception(
                "Error searching for business objects '{}'. HTTP:{} '{}' - {}".
                format(filter_object.business_object_name,
                       result_search.status_code, result_search.reason,
                       result_search.text))

        # Return the number of object and the array of objects
        return rows, business_objects