def override_clients(self, new_clients):
        """
        Replaces the service clients with a new set of them... and makes sure they go to the right places
        """

        #shortcut names for the import sub-services
        if hasattr(self.clients, "resource_registry"):
            self.RR    = self.clients.resource_registry
            
        if hasattr(self.clients, "data_acquisition_management_service"):
            self.DAMS  = self.clients.data_acquisition_management_service

        #farm everything out to the impls

        self.instrument_agent           = InstrumentAgentImpl(self.clients)
        self.instrument_agent_instance  = InstrumentAgentInstanceImpl(self.clients)
        self.instrument_model           = InstrumentModelImpl(self.clients)
        self.instrument_device          = InstrumentDeviceImpl(self.clients)

        self.platform_agent           = PlatformAgentImpl(self.clients)
        self.platform_agent_instance  = PlatformAgentInstanceImpl(self.clients)
        self.platform_model           = PlatformModelImpl(self.clients)
        self.platform_device          = PlatformDeviceImpl(self.clients)

        self.sensor_model    = SensorModelImpl(self.clients)
        self.sensor_device   = SensorDeviceImpl(self.clients)
    def override_clients(self, new_clients):
        """
        Replaces the service clients with a new set of them... and makes sure they go to the right places
        """

        #shortcut names for the import sub-services
        if hasattr(self.clients, "resource_registry"):
            self.RR = self.clients.resource_registry

        if hasattr(self.clients, "instrument_management"):
            self.IMS = self.clients.instrument_management

        #farm everything out to the impls

        self.org = OrgImpl(self.clients)
        self.observatory = ObservatoryImpl(self.clients)
        self.subsite = SubsiteImpl(self.clients)
        self.platform_site = PlatformSiteImpl(self.clients)
        self.instrument_site = InstrumentSiteImpl(self.clients)

        self.instrument_device = InstrumentDeviceImpl(self.clients)
        self.platform_device = PlatformDeviceImpl(self.clients)
    def override_clients(self, new_clients):
        """
        Replaces the service clients with a new set of them... and makes sure they go to the right places
        """

        #shortcut names for the import sub-services
        if hasattr(self.clients, "resource_registry"):
            self.RR    = self.clients.resource_registry
            
        if hasattr(self.clients, "instrument_management"):
            self.IMS   = self.clients.instrument_management

        if hasattr(self.clients, "data_process_management"):
            self.PRMS  = self.clients.data_process_management

        #farm everything out to the impls

        self.observatory      = ObservatoryImpl(self.clients)
        self.subsite          = SubsiteImpl(self.clients)
        self.platform_site    = PlatformSiteImpl(self.clients)
        self.instrument_site  = InstrumentSiteImpl(self.clients)

        self.instrument_device   = InstrumentDeviceImpl(self.clients)
        self.platform_device     = PlatformDeviceImpl(self.clients)
class InstrumentManagementService(BaseInstrumentManagementService):
    """
    @brief Service to manage instrument, platform, and sensor resources, their relationships, and direct access

    """
    def on_init(self):
        IonObject("Resource")  # suppress pyflakes error

        self.override_clients(self.clients)

    def override_clients(self, new_clients):
        """
        Replaces the service clients with a new set of them... and makes sure they go to the right places
        """

        #shortcut names for the import sub-services
        if hasattr(self.clients, "resource_registry"):
            self.RR    = self.clients.resource_registry
            
        if hasattr(self.clients, "data_acquisition_management_service"):
            self.DAMS  = self.clients.data_acquisition_management_service

        #farm everything out to the impls

        self.instrument_agent           = InstrumentAgentImpl(self.clients)
        self.instrument_agent_instance  = InstrumentAgentInstanceImpl(self.clients)
        self.instrument_model           = InstrumentModelImpl(self.clients)
        self.instrument_device          = InstrumentDeviceImpl(self.clients)

        self.platform_agent           = PlatformAgentImpl(self.clients)
        self.platform_agent_instance  = PlatformAgentInstanceImpl(self.clients)
        self.platform_model           = PlatformModelImpl(self.clients)
        self.platform_device          = PlatformDeviceImpl(self.clients)

        self.sensor_model    = SensorModelImpl(self.clients)
        self.sensor_device   = SensorDeviceImpl(self.clients)



    ##########################################################################
    #
    # INSTRUMENT AGENT INSTANCE
    #
    ##########################################################################

    def create_instrument_agent_instance(self, instrument_agent_instance=None):
        """
        create a new instance
        @param instrument_agent_instance the object to be created as a resource
        @retval instrument_agent_instance_id the id of the new object
        @throws BadRequest if the incoming _id field is set
        @throws BadReqeust if the incoming name already exists
        """
        return self.instrument_agent_instance.create_one(instrument_agent_instance)

    def update_instrument_agent_instance(self, instrument_agent_instance=None):
        """
        update an existing instance
        @param instrument_agent_instance the object to be created as a resource
        @retval success whether we succeeded
        @throws BadRequest if the incoming _id field is not set
        @throws BadReqeust if the incoming name already exists
        """
        return self.instrument_agent_instance.update_one(instrument_agent_instance)


    def read_instrument_agent_instance(self, instrument_agent_instance_id=''):
        """
        fetch a resource by ID
        @param instrument_agent_instance_id the id of the object to be fetched
        @retval InstrumentAgentInstance resource
        """
        return self.instrument_agent_instance.read_one(instrument_agent_instance_id)

    def delete_instrument_agent_instance(self, instrument_agent_instance_id=''):
        """
        delete a resource, including its history (for less ominous deletion, use retire)
        @param instrument_agent_instance_id the id of the object to be deleted
        @retval success whether it succeeded

        """
        return self.instrument_agent_instance.delete_one(instrument_agent_instance_id)

    def find_instrument_agent_instances(self, filters=None):
        """

        """
        return self.instrument_agent_instance.find_some(filters)




    ##########################################################################
    #
    # INSTRUMENT AGENT
    #
    ##########################################################################

    def create_instrument_agent(self, instrument_agent=None):
        """
        create a new instance
        @param instrument_agent the object to be created as a resource
        @retval instrument_agent_id the id of the new object
        @throws BadRequest if the incoming _id field is set
        @throws BadReqeust if the incoming name already exists
        """
        return self.instrument_agent.create_one(instrument_agent)

    def update_instrument_agent(self, instrument_agent=None):
        """
        update an existing instance
        @param instrument_agent the object to be created as a resource
        @retval success whether we succeeded
        @throws BadRequest if the incoming _id field is not set
        @throws BadReqeust if the incoming name already exists
        """
        return self.instrument_agent.update_one(instrument_agent)


    def read_instrument_agent(self, instrument_agent_id=''):
        """
        fetch a resource by ID
        @param instrument_agent_id the id of the object to be fetched
        @retval InstrumentAgent resource
        """
        return self.instrument_agent.read_one(instrument_agent_id)

    def delete_instrument_agent(self, instrument_agent_id=''):
        """
        delete a resource, including its history (for less ominous deletion, use retire)
        @param instrument_agent_id the id of the object to be deleted
        @retval success whether it succeeded

        """
        return self.instrument_agent.delete_one(instrument_agent_id)

    def find_instrument_agents(self, filters=None):
        """

        """
        return self.instrument_agent.find_some(filters)

    #FIXME: args need to change
    def assign_instrument_agent(self, instrument_agent_id='', instrument_id='', instrument_agent_instance=None):
        """
        @todo the arguments for this function seem incorrect and/or mismatched
        """
        raise NotImplementedError()
        #return self.instrument_agent.assign(instrument_agent_id, instrument_id, instrument_agent_instance)

    #FIXME: args need to change
    def unassign_instrument_agent(self, instrument_agent_id='', instrument_id=''):

        """
        @todo the arguments for this function seem incorrect and/or mismatched

        """
        raise NotImplementedError()
        #return self.instrument_agent.unassign(instrument_agent_id, instrument_device_id, instrument_agent_instance)




    ##########################################################################
    #
    # INSTRUMENT MODEL
    #
    ##########################################################################

    def create_instrument_model(self, instrument_model=None):
        """
        create a new instance
        @param instrument_model the object to be created as a resource
        @retval instrument_model_id the id of the new object
        @throws BadRequest if the incoming _id field is set
        @throws BadReqeust if the incoming name already exists
        """
        return self.instrument_model.create_one(instrument_model)

    def update_instrument_model(self, instrument_model=None):
        """
        update an existing instance
        @param instrument_model the object to be created as a resource
        @retval success whether we succeeded
        @throws BadRequest if the incoming _id field is not set
        @throws BadReqeust if the incoming name already exists
        """
        return self.instrument_model.update_one(instrument_model)


    def read_instrument_model(self, instrument_model_id=''):
        """
        fetch a resource by ID
        @param instrument_model_id the id of the object to be fetched
        @retval InstrumentModel resource
        """
        return self.instrument_model.read_one(instrument_model_id)

    def delete_instrument_model(self, instrument_model_id=''):
        """
        delete a resource, including its history (for less ominous deletion, use retire)
        @param instrument_model_id the id of the object to be deleted
        @retval success whether it succeeded

        """
        return self.instrument_model.delete_one(instrument_model_id)

    def find_instrument_models(self, filters=None):
        """

        """
        return self.instrument_model.find_some(filters)

    def assign_instrument_model(self, instrument_model_id='', instrument_device_id=''):
        """
        Assign a model to a device type
        @param instrument_model_id the model id
        @param instrument_device_id the device id
        @retval success whether there was success
        """
        return self.instrument_device.link_model(instrument_device_id, instrument_model_id)

    def unassign_instrument_model(self, instrument_model_id='', instrument_device_id=''):
        """
        Assign a model from a device type
        @param instrument_model_id the model id
        @param instrument_device_id the device id
        @retval success whether there was success

        """
        return self.instrument_device.unlink_model(instrument_device_id, instrument_model_id)








    ##########################################################################
    #
    # PHYSICAL INSTRUMENT
    #
    ##########################################################################

    def create_instrument_device(self, instrument_device=None):
        """
        create a new instance
        @param instrument_device the object to be created as a resource
        @retval instrument_device_id the id of the new object
        @throws BadRequest if the incoming _id field is set
        @throws BadReqeust if the incoming name already exists
        """
        return self.instrument_device.create_one(instrument_device)

    def update_instrument_device(self, instrument_device=None):
        """
        update an existing instance
        @param instrument_device the object to be created as a resource
        @retval success whether we succeeded
        @throws BadRequest if the incoming _id field is not set
        @throws BadReqeust if the incoming name already exists
        """
        return self.instrument_device.update_one(instrument_device)


    def read_instrument_device(self, instrument_device_id=''):
        """
        fetch a resource by ID
        @param instrument_device_id the id of the object to be fetched
        @retval InstrumentDevice resource

        """
        return self.instrument_device.read_one(instrument_device_id)

    def delete_instrument_device(self, instrument_device_id=''):
        """
        delete a resource, including its history (for less ominous deletion, use retire)
        @param instrument_device_id the id of the object to be deleted
        @retval success whether it succeeded

        """
        return self.instrument_device.delete_one(instrument_device_id)

    def find_instrument_devices(self, filters=None):
        """

        """
        return self.instrument_device.find_some(filters)




    ##################### INSTRUMENT LIFECYCLE ADVANCEMENT_ACTIONS




    def plan_instrument_device(self, name='', description='', instrument_model_id=''):
        """
        Plan an instrument: at this point, we know only its name, description, and model
        @todo this state may no longer be valid due to changes in available lifecycle states
        """

        #create the new resource
        new_inst_obj = IonObject("InstrumentDevice",
                                 name=name,
                                 description=description)
        instrument_device_id = self.instrument_device.create_one(instrument_device=new_inst_obj)

        #associate the model
        self.link_model(instrument_device_id, instrument_model_id)

        #move the association
        self.instrument_device.advance_lcs(instrument_device_id, LCE.register)

        return self.instrument_device._return_create("instrument_device_id", instrument_device_id)


    def acquire_instrument_device(self, instrument_device_id='', serialnumber='', firmwareversion='', hardwareversion=''):
        """
        When physical instrument is acquired, create all data products
        @todo this state may no longer be valid due to changes in available lifecycle states
        """

        #read instrument
        inst_obj = self.instrument_device.read(instrument_device_id=instrument_device_id)

        #update instrument with new params
        inst_obj.serialnumber     = serialnumber
        inst_obj.firmwareversion  = firmwareversion
        inst_obj.hardwareversion  = hardwareversion

        #FIXME: check this for an error
        self.instrument_device.update(instrument_device_id, inst_obj)


        #get data producer id from data acquisition management service
        pducer_id = self.DAMS.register_instrument(instrument_id=instrument_device_id)

        # associate data product with instrument
        self.instrument_device.link_data_producer(instrument_device_id, pducer_id)

        # set up the rest of the associations
        setup_dp_result = self.instrument_device.setup_data_production_chain(instrument_device_id)

        #FIXME: lifecycle state transition?

        return self.instrument_device._return_update(setup_dp_result)


    def develop_instrument_device(self, instrument_device_id='', instrument_agent_id=''):
        """
        Assign an instrument agent (just the type, not the instance) to an instrument
        @todo this state may no longer be valid due to changes in available lifecycle states
        """

        #FIXME: only valid in 'ACQUIRED' state!

        #FIXME: what to associate here?

        self.instrument_device.advance_lcs(instrument_device_id, LCE.develop)

        #FIXME: error checking

        return self.instrument_device._return_update(True)


    def commission_instrument_device(self, instrument_device_id='', platform_device_id=''):
        """
        @todo this state may no longer be valid due to changes in available lifecycle states

        """
        #FIXME: only valid in 'DEVELOPED' state!

        #FIXME: there seems to be no association between instruments and platforms

        self.instrument_device.advance_lcs(instrument_device_id, LCE.commission)

        return self.instrument_device._return_update(True)


    def decommission_instrument_device(self, instrument_device_id=''):
        """
        @todo this state may no longer be valid due to changes in available lifecycle states

        """
        #FIXME: only valid in 'COMMISSIONED' state!

        #FIXME: there seems to be no association between instruments and platforms
        self.instrument_device.advance_lcs(instrument_device_id, LCE.decommission)

        return self.instrument_device_return_update(True)


    def activate_instrument_device(self, instrument_device_id='', instrument_agent_instance_id=''):
        """
        @todo this state may no longer be valid due to changes in available lifecycle states

        """
        #FIXME: only valid in 'COMMISSIONED' state!

        #FIXME: validate somehow

        self.instrument_device.link_agent_instance(instrument_device_id, instrument_agent_instance_id)

        self.instrument_device.advance_lcs(instrument_device_id, LCE.activate)

        self.instrument_device._return_activate(True)

    def deactivate_instrument_device(self, instrument_device_id=''):
        """
        @todo this state may no longer be valid due to changes in available lifecycle states

        """

        #FIXME: only valid in 'ACTIVE' state!

        #FIXME: remove association

        self.instrument_device.advance_lcs(instrument_device_id, LCE.deactivate)

        return self.instrument_device._return_update(True)

    def retire_instrument_device(self, instrument_device_id=''):
        """
        Retire an instrument
        @todo this state may no longer be valid due to changes in available lifecycle states
        """

        #FIXME: what happens to logical instrument, platform, etc

        self.instrument_device.advance_lcs(instrument_device_id, LCE.retire)

        return self.instrument_device._return_update(True)




    def assign_instrument_device(self, instrument_id='', instrument_device_id=''):
        """
        @todo the arguments to this function have the wrong names

        """
        # Return Value
        # ------------
        # {success: true}
        #
        raise NotImplementedError()
        logical_instrument_id = ''
        return self.instrument_device.link_assignment(instrument_device_id, logical_instrument_id)

    def unassign_instrument_device(self, instrument_id='', instrument_device_id=''):
        """
        @todo the arguments to this function have the wrong names

        """
        # Return Value
        # ------------
        # {success: true}
        #
        raise NotImplementedError()
        logical_instrument_id = ''
        return self.instrument_device.unlink_assignment(instrument_device_id, logical_instrument_id)





    ##
    ##
    ##  DIRECT ACCESS
    ##
    ##

    def request_direct_access(self, instrument_device_id=''):
        """

        """

        # determine whether id is for physical or logical instrument
        # look up instrument if not

        # Validate request; current instrument state, policy, and other

        # Retrieve and save current instrument settings

        # Request DA channel, save reference

        # Return direct access channel
        raise NotImplementedError()
        pass

    def stop_direct_access(self, instrument_device_id=''):
        """

        """
        # Return Value
        # ------------
        # {success: true}
        #
        raise NotImplementedError()
        pass







    ##########################################################################
    #
    # PLATFORM AGENT INSTANCE
    #
    ##########################################################################

    def create_platform_agent_instance(self, platform_agent_instance=None):
        """
        create a new instance
        @param platform_agent_instance the object to be created as a resource
        @retval platform_agent_instance_id the id of the new object
        @throws BadRequest if the incoming _id field is set
        @throws BadReqeust if the incoming name already exists
        """
        return self.platform_agent_instance.create_one(platform_agent_instance)

    def update_platform_agent_instance(self, platform_agent_instance=None):
        """
        update an existing instance
        @param platform_agent_instance the object to be created as a resource
        @retval success whether we succeeded
        @throws BadRequest if the incoming _id field is not set
        @throws BadReqeust if the incoming name already exists
        """
        return self.platform_agent_instance.update_one(platform_agent_instance)


    def read_platform_agent_instance(self, platform_agent_instance_id=''):
        """
        fetch a resource by ID
        @param platform_agent_instance_id the id of the object to be fetched
        @retval PlatformAgentInstance resource
        """
        return self.platform_agent_instance.read_one(platform_agent_instance_id)

    def delete_platform_agent_instance(self, platform_agent_instance_id=''):
        """
        delete a resource, including its history (for less ominous deletion, use retire)
        @param platform_agent_instance_id the id of the object to be deleted
        @retval success whether it succeeded

        """
        return self.platform_agent_instance.delete_one(platform_agent_instance_id)

    def find_platform_agent_instances(self, filters=None):
        """

        """
        return self.platform_agent_instance.find_some(filters)






    ##########################################################################
    #
    # PLATFORM AGENT
    #
    ##########################################################################


    def create_platform_agent(self, platform_agent=None):
        """
        create a new instance
        @param platform_agent the object to be created as a resource
        @retval platform_agent_id the id of the new object
        @throws BadRequest if the incoming _id field is set
        @throws BadReqeust if the incoming name already exists
        """
        return self.platform_agent.create_one(platform_agent)

    def update_platform_agent(self, platform_agent=None):
        """
        update an existing instance
        @param platform_agent the object to be created as a resource
        @retval success whether we succeeded
        @throws BadRequest if the incoming _id field is not set
        @throws BadReqeust if the incoming name already exists

        """
        return self.platform_agent.update_one(platform_agent)


    def read_platform_agent(self, platform_agent_id=''):
        """
        fetch a resource by ID
        @param platform_agent_id the id of the object to be fetched
        @retval PlatformAgent resource

        """
        return self.platform_agent.read_one(platform_agent_id)

    def delete_platform_agent(self, platform_agent_id=''):
        """
        delete a resource, including its history (for less ominous deletion, use retire)
        @param platform_agent_id the id of the object to be deleted
        @retval success whether it succeeded

        """
        return self.platform_agent.delete_one(platform_agent_id)

    def find_platform_agents(self, filters=None):
        """

        """
        return self.platform_agent.find_some(filters)

    #FIXME: args need to change
    def assign_platform_agent(self, platform_agent_id='', platform_id='', platform_agent_instance=None):
        """
        @todo the arguments for this function seem incorrect and/or mismatched
        """
        raise NotImplementedError()
        #return self.platform_agent.assign(platform_agent_id, platform_id, platform_agent_instance)

    #FIXME: args need to change
    def unassign_platform_agent(self, platform_agent_id='', platform_id=''):

        """
        @todo the arguments for this function seem incorrect and/or mismatched

        """
        raise NotImplementedError()
        #return self.platform_agent.unassign(platform_agent_id, platform_device_id, platform_agent_instance)




    ##########################################################################
    #
    # PLATFORM MODEL
    #
    ##########################################################################


    def create_platform_model(self, platform_model=None):
        """
        create a new instance
        @param platform_model the object to be created as a resource
        @retval platform_model_id the id of the new object
        @throws BadRequest if the incoming _id field is set
        @throws BadReqeust if the incoming name already exists
        """
        return self.platform_model.create_one(platform_model)

    def update_platform_model(self, platform_model=None):
        """
        update an existing instance
        @param platform_model the object to be created as a resource
        @retval success whether we succeeded
        @throws BadRequest if the incoming _id field is not set
        @throws BadReqeust if the incoming name already exists
        """
        return self.platform_model.update_one(platform_model)


    def read_platform_model(self, platform_model_id=''):
        """
        fetch a resource by ID
        @param platform_model_id the id of the object to be fetched
        @retval PlatformModel resource

        """
        return self.platform_model.read_one(platform_model_id)

    def delete_platform_model(self, platform_model_id=''):
        """
        delete a resource, including its history (for less ominous deletion, use retire)
        @param platform_model_id the id of the object to be deleted
        @retval success whether it succeeded

        """
        return self.platform_model.delete_one(platform_model_id)

    def find_platform_models(self, filters=None):
        """

        """
        return self.platform_model.find_some(filters)

    def assign_platform_model(self, platform_model_id='', platform_device_id=''):
        """
        @todo this seems backwards
        """
        return self.platform_model.assign(platform_model_id, platform_device_id)

    def unassign_platform_model(self, platform_model_id='', platform_device_id=''):
        """
        @todo this seems backwards
        """
        return self.platform_model.assign(platform_model_id, platform_device_id)






    ##########################################################################
    #
    # PHYSICAL PLATFORM
    #
    ##########################################################################



    def create_platform_device(self, platform_device=None):
        """
        create a new instance
        @param platform_device the object to be created as a resource
        @retval platform_device_id the id of the new object
        @throws BadRequest if the incoming _id field is set
        @throws BadReqeust if the incoming name already exists
        """
        return self.platform_device.create_one(platform_device)

    def update_platform_device(self, platform_device=None):
        """
        update an existing instance
        @param platform_device the object to be created as a resource
        @retval success whether we succeeded
        @throws BadRequest if the incoming _id field is not set
        @throws BadReqeust if the incoming name already exists

        """
        return self.platform_device.update_one(platform_device)


    def read_platform_device(self, platform_device_id=''):
        """
        fetch a resource by ID
        @param platform_device_id the id of the object to be fetched
        @retval PlatformDevice resource

        """
        return self.platform_device.read_one(platform_device_id)

    def delete_platform_device(self, platform_device_id=''):
        """
        delete a resource, including its history (for less ominous deletion, use retire)
        @param platform_device_id the id of the object to be deleted
        @retval success whether it succeeded

        """
        return self.platform_device.delete_one(platform_device_id)

    def find_platform_devices(self, filters=None):
        """

        """
        return self.platform_device.find_some(filters)






    ##########################################################################
    #
    # SENSOR MODEL
    #
    ##########################################################################


    def create_sensor_model(self, sensor_model=None):
        """
        create a new instance
        @param sensor_model the object to be created as a resource
        @retval sensor_model_id the id of the new object
        @throws BadRequest if the incoming _id field is set
        @throws BadReqeust if the incoming name already exists
        """
        return self.sensor_model.create_one(sensor_model)

    def update_sensor_model(self, sensor_model=None):
        """
        update an existing instance
        @param sensor_model the object to be created as a resource
        @retval success whether we succeeded
        @throws BadRequest if the incoming _id field is not set
        @throws BadReqeust if the incoming name already exists

        """
        return self.sensor_model.update_one(sensor_model)


    def read_sensor_model(self, sensor_model_id=''):
        """
        fetch a resource by ID
        @param sensor_model_id the id of the object to be fetched
        @retval SensorModel resource

        """
        return self.sensor_model.read_one(sensor_model_id)

    def delete_sensor_model(self, sensor_model_id=''):
        """
        delete a resource, including its history (for less ominous deletion, use retire)
        @param sensor_model_id the id of the object to be deleted
        @retval success whether it succeeded

        """
        return self.sensor_model.delete_one(sensor_model_id)

    def find_sensor_models(self, filters=None):
        """

        """
        return self.sensor_model.find_some(filters)

    def assign_sensor_model(self, sensor_model_id='', sensor_device_id=''):
        """
        @todo this seems backwards... should be device, model
        """
        return self.sensor_model.assign(sensor_model_id, sensor_device_id)

    def unassign_sensor_model(self, sensor_model_id='', sensor_device_id=''):
        """
        @todo this seems backwards... should be device, model

        """
        return self.sensor_model.unassign(sensor_model_id, sensor_device_id)




    ##########################################################################
    #
    # PHYSICAL SENSOR
    #
    ##########################################################################



    def create_sensor_device(self, sensor_device=None):
        """
        create a new instance
        @param sensor_device the object to be created as a resource
        @retval sensor_device_id the id of the new object
        @throws BadRequest if the incoming _id field is set
        @throws BadReqeust if the incoming name already exists
        """
        return self.sensor_device.create_one(sensor_device)

    def update_sensor_device(self, sensor_device=None):
        """
        update an existing instance
        @param sensor_device the object to be created as a resource
        @retval success whether we succeeded
        @throws BadRequest if the incoming _id field is not set
        @throws BadReqeust if the incoming name already exists

        """
        return self.sensor_device.update_one(sensor_device)


    def read_sensor_device(self, sensor_device_id=''):
        """
        fetch a resource by ID
        @param sensor_device_id the id of the object to be fetched
        @retval SensorDevice resource

        """
        return self.sensor_device.read_one(sensor_device_id)

    def delete_sensor_device(self, sensor_device_id=''):
        """
        delete a resource, including its history (for less ominous deletion, use retire)
        @param sensor_device_id the id of the object to be deleted
        @retval success whether it succeeded

        """
        return self.sensor_device.delete_one(sensor_device_id)

    def find_sensor_devices(self, filters=None):
        """

        """
        return self.sensor_device.find_some(filters)
class ObservatoryManagementService(BaseObservatoryManagementService):


    def on_init(self):
        IonObject("Resource")  # suppress pyflakes error
        CFG, log, RT, PRED, LCS, LCE, NotFound, BadRequest, log  #suppress pyflakes errors about "unused import"

        self.override_clients(self.clients)

        self.HIERARCHY_DEPTH = {RT.InstrumentSite: 3,
                                RT.PlatformSite: 2,
                                RT.Subsite: 1,
                                RT.Observatory: 0,
                                }
        
        self.HIERARCHY_LOOKUP = [RT.Observatory, 
                                 RT.Subsite, 
                                 RT.PlatformSite, 
                                 RT.InstrumentSite]



    def override_clients(self, new_clients):
        """
        Replaces the service clients with a new set of them... and makes sure they go to the right places
        """

        #shortcut names for the import sub-services
        if hasattr(self.clients, "resource_registry"):
            self.RR    = self.clients.resource_registry
            
        if hasattr(self.clients, "instrument_management"):
            self.IMS   = self.clients.instrument_management

        if hasattr(self.clients, "data_process_management"):
            self.PRMS  = self.clients.data_process_management

        #farm everything out to the impls

        self.observatory      = ObservatoryImpl(self.clients)
        self.subsite          = SubsiteImpl(self.clients)
        self.platform_site    = PlatformSiteImpl(self.clients)
        self.instrument_site  = InstrumentSiteImpl(self.clients)

        self.instrument_device   = InstrumentDeviceImpl(self.clients)
        self.platform_device     = PlatformDeviceImpl(self.clients)



    
    ##########################################################################
    #
    # CRUD OPS
    #
    ##########################################################################


    def create_marine_facility(self, org=None):
        """Create an Org (domain of authority) that realizes a marine facility. This Org will have
        set up roles for a marine facility. Shared resources, such as a device can only be
        registered in one marine facility Org, and additionally in many virtual observatory Orgs. The
        marine facility operators will have more extensive permissions and will supercede virtual
        observatory commands

        @param org    Org
        @retval org_id    str
        @throws BadRequest    if object does not have _id or _rev attribute
        @throws NotFound    object with specified id does not exist
        """
        log.debug("ObservatoryManagementService.create_marine_facility(): %s" % org)
        
        # create the org
        org.org_type = OrgTypeEnum.MARINE_FACILITY
        org_id = self.clients.org_management.create_org(org)

        #Instantiate initial set of User Roles for this marine facility
        instrument_operator_role = IonObject(RT.UserRole, name=INSTRUMENT_OPERATOR_ROLE, 
                                             label='Instrument Operator', description='Marine Facility Instrument Operator')
        self.clients.org_management.add_user_role(org_id, instrument_operator_role)
        observatory_operator_role = IonObject(RT.UserRole, name=OBSERVATORY_OPERATOR_ROLE, 
                                             label='Observatory Operator', description='Marine Facility Observatory Operator')
        self.clients.org_management.add_user_role(org_id, observatory_operator_role)
        data_operator_role = IonObject(RT.UserRole, name=DATA_OPERATOR_ROLE, 
                                             label='Data Operator', description='Marine Facility Data Operator')
        self.clients.org_management.add_user_role(org_id, data_operator_role)
        
        return org_id

    def create_virtual_observatory(self, org=None):
        """Create an Org (domain of authority) that realizes a virtual observatory. This Org will have
        set up roles for a virtual observatory. Shared resources, such as a device can only be
        registered in one marine facility Org, and additionally in many virtual observatory Orgs. The
        marine facility operators will have more extensive permissions and will supercede virtual
        observatory commands

        @param org    Org
        @retval org_id    str
        @throws BadRequest    if object does not have _id or _rev attribute
        @throws NotFound    object with specified id does not exist
        """
        log.debug("ObservatoryManagementService.create_virtual_observatory(): %s" % org)

        # create the org
        org.org_type = OrgTypeEnum.VIRTUAL_OBSERVATORY
        org_id = self.clients.org_management.create_org(org)

        return org_id


    def create_observatory(self, observatory=None):
        """Create a Observatory resource. An observatory  is coupled
        with one Org. The Org is created and associated as part of this call.

        @param observatory    Observatory
        @retval observatory_id    str
        @throws BadRequest    if object does not have _id or _rev attribute
        @throws NotFound    object with specified id does not exist
        """

        # create the marine facility
        observatory_id = self.observatory.create_one(observatory)

        return observatory_id

    def read_observatory(self, observatory_id=''):
        """Read a Observatory resource

        @param observatory_id    str
        @retval observatory    Observatory
        @throws NotFound    object with specified id does not exist
        """
        return self.observatory.read_one(observatory_id)

    def update_observatory(self, observatory=None):
        """Update a Observatory resource

        @param observatory    Observatory
        @throws NotFound    object with specified id does not exist
        """
        return self.observatory.update_one(observatory)

    def delete_observatory(self, observatory_id=''):
        """Delete a Observatory resource

        @param observatory_id    str
        @throws NotFound    object with specified id does not exist
        """
        return self.observatory.delete_one(observatory_id)


    def create_subsite(self, subsite=None, parent_id=''):
        """Create a Subsite resource. A subsite is a frame of reference within an observatory. Its parent is
        either the observatory or another subsite.

        @param subsite    Subsite
        @param parent_id    str
        @retval subsite_id    str
        @throws BadRequest    if object does not have _id or _rev attribute
        @throws NotFound    object with specified id does not exist
        """
        subsite_id = self.subsite.create_one(subsite)

        if parent_id:
            self.subsite.link_parent(subsite_id, parent_id)

        return subsite_id

    def read_subsite(self, subsite_id=''):
        """Read a Subsite resource

        @param subsite_id    str
        @retval subsite    Subsite
        @throws NotFound    object with specified id does not exist
        """
        return self.subsite.read_one(subsite_id)

    def update_subsite(self, subsite=None):
        """Update a Subsite resource

        @param subsite    Subsite
        @throws NotFound    object with specified id does not exist
        """
        return self.subsite.update_one(subsite)

    def delete_subsite(self, subsite_id=''):
        """Delete a subsite resource, removes assocations to parents

        @param subsite_id    str
        @throws NotFound    object with specified id does not exist
        """
        self.subsite.delete_one(subsite_id)



    def create_platform_site(self, platform_site=None, parent_id=''):
        """Create a PlatformSite resource. A platform_site is a frame of reference within an observatory. Its parent is
        either the observatory or another platform_site.

        @param platform_site    PlatformSite
        @param parent_id    str
        @retval platform_site_id    str
        @throws BadRequest    if object does not have _id or _rev attribute
        @throws NotFound    object with specified id does not exist
        """
        platform_site_id = self.platform_site.create_one(platform_site)

        if parent_id:
            self.platform_site.link_parent(platform_site_id, parent_id)

        return platform_site_id

    def read_platform_site(self, platform_site_id=''):
        """Read a PlatformSite resource

        @param platform_site_id    str
        @retval platform_site    PlatformSite
        @throws NotFound    object with specified id does not exist
        """
        return self.platform_site.read_one(platform_site_id)

    def update_platform_site(self, platform_site=None):
        """Update a PlatformSite resource

        @param platform_site    PlatformSite
        @throws NotFound    object with specified id does not exist
        """
        return self.platform_site.update_one(platform_site)

    def delete_platform_site(self, platform_site_id=''):
        """Delete a PlatformSite resource, removes assocations to parents

        @param platform_site_id    str
        @throws NotFound    object with specified id does not exist
        """
        self.platform_site.delete_one(platform_site_id)



    def create_instrument_site(self, instrument_site=None, parent_id=''):
        """Create a InstrumentSite resource. A instrument_site is a frame of reference within an observatory. Its parent is
        either the observatory or another instrument_site.

        @param instrument_site    InstrumentSite
        @param parent_id    str
        @retval instrument_site_id    str
        @throws BadRequest    if object does not have _id or _rev attribute
        @throws NotFound    object with specified id does not exist
        """
        instrument_site_id = self.instrument_site.create_one(instrument_site)

        if parent_id:
            self.instrument_site.link_parent(instrument_site_id, parent_id)

        return instrument_site_id

    def read_instrument_site(self, instrument_site_id=''):
        """Read a InstrumentSite resource

        @param instrument_site_id    str
        @retval instrument_site    InstrumentSite
        @throws NotFound    object with specified id does not exist
        """
        return self.instrument_site.read_one(instrument_site_id)

    def update_instrument_site(self, instrument_site=None):
        """Update a InstrumentSite resource

        @param instrument_site    InstrumentSite
        @throws NotFound    object with specified id does not exist
        """
        return self.instrument_site.update_one(instrument_site)

    def delete_instrument_site(self, instrument_site_id=''):
        """Delete a InstrumentSite resource, removes assocations to parents

        @param instrument_site_id    str
        @throws NotFound    object with specified id does not exist
        """
        # todo: give InstrumentSite a lifecycle in COI so that we can remove the "True" argument here
        self.instrument_site.delete_one(instrument_site_id)



    def create_deployment(self, deployment=None, site_id="", device_id=""):
        """
        Create a Deployment resource. Represents a (possibly open-ended) time interval
        grouping one or more resources within a given context, such as an instrument
        deployment on a platform at an observatory site.
        """

        deployment_id, version = self.clients.resource_registry.create(deployment)

        #Verify that site and device exist, add links if they do
        if site_id:
            site_obj = self.clients.resource_registry.read(site_id)
            if site_obj:
                self.clients.resource_registry.create_association(site_id, PRED.hasDeployment, deployment_id)

        if device_id:
            device_obj = self.clients.resource_registry.read(device_id)
            if device_obj:
                self.clients.resource_registry.create_association(device_id, PRED.hasDeployment, deployment_id)

        return deployment_id

    def update_deployment(self, deployment=None):
        # Overwrite Deployment object
        self.clients.resource_registry.update(deployment)

    def read_deployment(self, deployment_id=''):
        # Read Deployment object with _id matching id
        log.debug("Reading Deployment object id: %s" % deployment_id)
        deployment_obj = self.clients.resource_registry.read(deployment_id)

        return deployment_obj

    def delete_deployment(self, deployment_id=''):
        """
        Delete a Deployment resource
        """
        #Verify that the deployment exist
        deployment_obj = self.clients.resource_registry.read(deployment_id)
        if not deployment_obj:
            raise NotFound("Deployment  %s does not exist" % deployment_id)

        # Remove the link between the Stream Definition resource and the Data Process Definition resource
        associations = self.clients.resource_registry.find_associations(None, PRED.hasDeployment, deployment_id, id_only=True)
        if not associations:
            raise NotFound("No Sites or Devices associated with this Deployment identifier " + str(deployment_id))
        for association in associations:
            self.clients.resource_registry.delete_association(association)

        # Delete the deployment
        self.clients.resource_registry.delete(deployment_id)


    ############################
    #
    #  ASSOCIATIONS
    #
    ############################


    def assign_site_to_site(self, child_site_id='', parent_site_id=''):
        """Connects a child site (any subtype) to a parent site (any subtype)

        @param child_site_id    str
        @param parent_site_id    str
        @throws NotFound    object with specified id does not exist
        """
        parent_site_obj = self.subsite.read_one(parent_site_id)
        parent_site_type = parent_site_obj._get_type()

        if RT.Observatory == parent_site_type:
            self.observatory.link_site(parent_site_id, child_site_id)
        elif RT.Subsite == parent_site_type:
           self.subsite.link_site(parent_site_id, child_site_id)
        elif RT.PlatformSite == parent_site_type:
           self.platform_site.link_site(parent_site_id, child_site_id)
        else:
           raise BadRequest("Tried to assign a child site to a %s resource" % parent_site_type)

    def unassign_site_from_site(self, child_site_id='', parent_site_id=''):
        """Disconnects a child site (any subtype) from a parent site (any subtype)

        @param child_site_id    str
        @param parent_site_id    str
        @throws NotFound    object with specified id does not exist
        """
        parent_site_obj = self.subsite.read_one(parent_site_id)
        parent_site_type = parent_site_obj._get_type()

        if RT.Observatory == parent_site_type:
            self.observatory.unlink_site(parent_site_id, child_site_id)
        elif RT.Subsite == parent_site_type:
            self.subsite.unlink_site(parent_site_id, child_site_id)
        elif RT.PlatformSite == parent_site_type:
            self.platform_site.unlink_site(parent_site_id, child_site_id)
        else:
            raise BadRequest("Tried to unassign a child site from a %s resource" % parent_site_type)

    def assign_device_to_site(self, device_id='', site_id=''):
        """Connects a device (any type) to a site (any subtype)

        @param device_id    str
        @param site_id    str
        @throws NotFound    object with specified id does not exist
        """
        site_obj = self.subsite.read_one(site_id)
        site_type = site_obj._get_type()

        if RT.PlatformSite == site_type:
           self.platform_site.link_device(site_id, device_id)
        elif RT.InstrumentSite == site_type:
           self.instrument_site.link_device(site_id, device_id)
        else:
           raise BadRequest("Tried to assign a device to a %s resource" % site_type)

    def unassign_device_from_site(self, device_id='', site_id=''):
        """Disconnects a device (any type) from a site (any subtype)

        @param device_id    str
        @param site_id    str
        @throws NotFound    object with specified id does not exist
        """
        site_obj = self.subsite.read_one(site_id)
        site_type = site_obj._get_type()

        if RT.PlatformSite == site_type:
           self.platform_site.unlink_device(site_id, device_id)
        elif RT.InstrumentSite == site_type:
           self.instrument_site.unlink_device(site_id, device_id)
        else:
           raise BadRequest("Tried to unassign a device from a %s resource" % site_type)

    def assign_site_to_observatory(self, site_id='', observatory_id=''):
        self.observatory.link_site(observatory_id, site_id)

    def unassign_site_from_observatory(self, site_id="", observatory_id=''):
        self.observatory.unlink_site(observatory_id, site_id)

    def assign_instrument_model_to_instrument_site(self, instrument_model_id='', instrument_site_id=''):
        self.instrument_site.link_model(instrument_site_id, instrument_model_id)

    def unassign_instrument_model_from_instrument_site(self, instrument_model_id='', instrument_site_id=''):
        self.instrument_site.unlink_model(instrument_site_id, instrument_model_id)

    def assign_platform_model_to_platform_site(self, platform_model_id='', platform_site_id=''):
        self.platform_site.link_model(platform_site_id, platform_model_id)

    def unassign_platform_model_from_platform_site(self, platform_model_id='', platform_site_id=''):
        self.platform_site.unlink_model(platform_site_id, platform_model_id)

    def assign_resource_to_observatory_org(self, resource_id='', org_id=''):
        if not org_id:
            raise BadRequest("Org id not given")
        if not resource_id:
            raise BadRequest("Resource id not given")

        log.debug("assign_resource_to_observatory_org: org_id=%s, resource_id=%s " % (org_id, resource_id))
        self.clients.org_management.share_resource(org_id, resource_id)

    def unassign_resource_from_observatory_org(self, resource_id='', org_id=''):
        if not org_id:
            raise BadRequest("Org id not given")
        if not resource_id:
            raise BadRequest("Resource id not given")

        self.clients.org_management.unshare_resource(org_id, resource_id)







    ##########################################################################
    #
    # DEPLOYMENTS
    #
    ##########################################################################



    def deploy_instrument_site(self, instrument_site_id='', deployment_id=''):
        self.instrument_site.link_deployment(instrument_site_id, deployment_id)

    def undeploy_instrument_site(self, instrument_site_id='', deployment_id=''):
        self.instrument_site.unlink_deployment(instrument_site_id, deployment_id)

    def deploy_platform_site(self, platform_site_id='', deployment_id=''):
        self.platform_site.link_deployment(platform_site_id, deployment_id)

    def undeploy_platform_site(self, platform_site_id='', deployment_id=''):
        self.platform_site.unlink_deployment(platform_site_id, deployment_id)


    def create_site_data_product(self, site_id="", data_product_id=""):
        # verify that both exist
        site_obj = self.RR.read(site_id)
        data_product_obj = self.RR.read(data_product_id)
        sitetype = type(site_obj).__name__

        if not (RT.InstrumentSite == sitetype or RT.PlatformSite == sitetype):
            raise BadRequest("Can't associate a data product to a %s" % sitetype)

        # validation
        prods, _ = self.RR.find_objects(site_id, PRED.hasOutputProduct, RT.DataProduct)
        if 0 < len(prods):
            raise BadRequest("%s '%s' already has an ouptut data product" % (sitetype, site_id))

        sites, _ = self.RR.find_subjects(sitetype, PRED.hasOutputProduct, data_product_id)
        if 0 < len(sites):
            raise BadRequest("DataProduct '%s' is already an output product of a %s" % (data_product_id, sitetype))

        #todo: re-use existing defintion?  how?
        log.info("Creating data process definition")
        dpd_obj = IonObject(RT.DataProcessDefinition,
                            name='logical_transform',
                            description='send the packet from the in stream to the out stream unchanged',
                            module='ion.processes.data.transforms.logical_transform',
                            class_name='logical_transform',
                            process_source="For %s '%s'" % (sitetype, site_id))

        logical_transform_dprocdef_id = self.PRMS.create_data_process_definition(dpd_obj)

        log.info("Creating data process")
        dproc_id = self.PRMS.create_data_process(logical_transform_dprocdef_id, [], {"output":data_product_id})
        log.info("Created data process")

        log.info("associating site hasOutputProduct")
        #make it all happen
        if RT.InstrumentSite == sitetype:
            self.instrument_site.link_output_product(site_id, data_product_id)
        elif RT.PlatformSite == sitetype:
            self.platform_site.link_output_product(site_id, data_product_id)




    def streamdef_of_site(self, site_id):
        """
        return the streamdef associated with the output product of a site
        """

        #assume we've previously validated that the site has 1 product
        p, _ = self.RR.find_objects(site_id, PRED.hasOutputProduct, RT.DataProduct, True)
        streams, _ = self.RR.find_objects(p[0], PRED.hasStream, RT.Stream, True)
        if 1 != len(streams):
            raise BadRequest("Expected 1 stream on DataProduct '%s', got %d" % (p[0], len(streams)))
        sdefs, _ = self.RR.find_objects(streams[0], PRED.hasStreamDefinition, RT.StreamDefinition, True)
        if 1 != len(sdefs):
            raise BadRequest("Expected 1 streamdef on StreamDefinition '%s', got %d" % (streams[0], len(sdefs)))

        return sdefs[0]


    def streamdefs_of_device(self, device_id):
        """
        return a dict of streamdef_id => stream_id for a given device
        """

        #recursive function to get all data producers
        def child_data_producers(dpdc_ids):
            def cdp_helper(acc2, dpdc_id2):
                children, _ = self.RR.find_subjects(RT.DataProducer, PRED.hasParent, dpdc_id2, True)
                for child in children:
                    acc2.append(child)
                    acc2 = cdp_helper(acc2, child)
                return acc

            #call helper using input list of data products
            acc = []
            for d in dpdc_ids:
                acc = cdp_helper(acc, d)
            return acc

        #initial list of data producers
        pdcs, _ = self.RR.find_objects(device_id, PRED.hasDataProducer, RT.DataProducer, True)
        if 0 == len(pdcs):
            raise BadRequest("Expected data producer(s) on device '%s', got none" % device_id)

        #now the full list of data producers, with children
        pdcs = child_data_producers(pdcs)
        log.debug("Got %d data producers" % len(pdcs))

        streamdefs = {}
        for pdc in pdcs:
            log.debug("Checking data prodcer %s" % pdc)
            prods, _ = self.RR.find_subjects(RT.DataProduct, PRED.hasDataProducer, pdc, True)
            for p in prods:
                log.debug("Checking product %s" % p)
                streams, _ = self.RR.find_objects(p, PRED.hasStream, RT.Stream, True)
                for s in streams:
                    log.debug("Checking stream %s" % s)
                    sdefs, _ = self.RR.find_objects(s, PRED.hasStreamDefinition, RT.StreamDefinition, True)
                    for sd in sdefs:
                        log.debug("Checking streamdef %s" % sd)
                        if sd in streamdefs:
                            raise BadRequest("Got a duplicate stream definition stemming from device %s" % device_id)
                        streamdefs[sd] = s

        return streamdefs

    def check_site_for_deployment(self, site_id, site_type, model_type):
        log.debug("checking %s for deployment, will return %s" % (site_type, model_type))
        # validate and return supported models
        models, _ = self.RR.find_objects(site_id, PRED.hasModel, model_type, True)
        if 1 > len(models):
            raise BadRequest("Expected at least 1 model for %s '%s', got %d" % (site_type, site_id, len(models)))

        log.debug("checking site data products")
        prods, _ = self.RR.find_objects(site_id, PRED.hasOutputProduct, RT.DataProduct, True)
        if 1 != len(prods):
            raise BadRequest("Expected 1 output data product on %s '%s', got %d" % (site_type, site_id, len(prods)))
        return models



    def check_device_for_deployment(self, device_id, device_type, model_type):
        log.debug("checking %s for deployment, will return %s" % (device_type, model_type))
        # validate and return model
        models, _ = self.RR.find_objects(device_id, PRED.hasModel, model_type, True)
        if 1 != len(models):
            raise BadRequest("Expected 1 model for %s '%s', got %d" % (device_type, device_id, len(models)))
        return models[0]

    def check_site_device_pair_for_deployment(self, site_id, device_id, site_type=None, device_type=None):
        log.debug("checking %s/%s pair for deployment" % (site_type, device_type))
        #return a pair that should be REMOVED, or None

        if site_type is None:
            site_type = type(self.RR.read(site_id)).__name__

        if device_type is None:
            device_type = type(self.RR.read(device_id)).__name__

        ret = None

        log.debug("checking existing hasDevice links from site")
        devices, _ = self.RR.find_objects(site_id, PRED.hasDevice, device_type, True)
        if 1 < len(devices):
            raise Inconsistent("Found more than 1 hasDevice relationship from %s '%s'" % (site_type, site_id))
        elif 0 < len(devices):
            if devices[0] != device_id:
                ret = (site_id, devices[0])
                log.info("%s '%s' is already hasDevice with a %s" % (site_type, site_id, device_type))

        return ret

    def has_matching_streamdef(self, site_id, device_id):
        if not self.streamdef_of_site(site_id) in self.streamdefs_of_device(device_id):
            raise BadRequest("No matching streamdefs between %s '%s' and %s '%s'" %
                             (site_type, site_id, device_type, device_id))

    def collect_deployment_components(self, deployment_id):
        """
        get all devices and sites associated with this deployment and use their ID as a key to list of models
        """

        device_models = {}
        site_models = {}

        # significant change-up in how this works.
        #
        # collect all devices in this deployment

        def add_sites(site_ids, site_type, model_type):
            for s in site_ids:
                models = self.check_site_for_deployment(s, site_type, model_type)
                if s in site_models:
                    log.warn("Site '%s' was already collected in deployment '%s'" % (s, deployment_id))
                site_models[s] = models

        def add_devices(device_ids, device_type, model_type):
            for d in device_ids:
                model = self.check_device_for_deployment(d, device_type, model_type)
                if d in device_models:
                    log.warn("Device '%s' was already collected in deployment '%s'" % (d, deployment_id))
                device_models[d] = model


        def collect_specific_resources(site_type, device_type, model_type):
            # check this deployment -- specific device types -- for validity
            # return a list of pairs (site, device) to be associated

            new_site_ids, _ = self.RR.find_subjects(site_type,
                                                    PRED.hasDeployment,
                                                    deployment_id,
                                                    True)

            new_device_ids, _ = self.RR.find_subjects(device_type,
                                                      PRED.hasDeployment,
                                                      deployment_id,
                                                      True)

            add_sites(new_site_ids, site_type, model_type)
            add_devices(new_device_ids, site_type, model_type)

        # collect platforms, verify that only one platform device exists in the deployment
        collect_specific_resources(RT.PlatformSite, RT.PlatformDevice, RT.PlatformModel)
        if 1 < len(device_models):
            raise BadRequest("Multiple platforms in the same deployment are not allowed")
        elif 0 < len(device_models):
            # add devices and sites that are children of platform device / site
            child_device_ids = self.platform_device.find_stemming_device(device_models.keys()[0])
            child_site_ids = self.find_related_frames_of_reference(site_models.keys()[0],
                [RT.PlatformSite, RT.InstrumentSite])

            # IGNORE child platforms
            #  verify that platform site has no sub-platform-sites
            #if 0 < len(child_site_ids[RT.PlatformSite]):
            #    raise BadRequest("Deploying a platform with its own child platform is not allowed")

            #  gather a list of all instrument sites on platform site
            #  gather a list of all instrument devices on platform device
            add_devices(child_device_ids, RT.InstrumentDevice, RT.InstrumentModel)
            add_sites(child_site_ids[RT.InstrumentSite], RT.InstrumentSite, RT.InstrumentModel)

        collect_specific_resources(RT.InstrumentSite, RT.InstrumentDevice, RT.InstrumentModel)

        return device_models, site_models



    def activate_deployment(self, deployment_id='', activate_subscriptions=False):
        """
        Make the devices on this deployment the primary devices for the sites
        """
        #Verify that the deployment exists
        deployment_obj = self.clients.resource_registry.read(deployment_id)

#        if LCS.DEPLOYED == deployment_obj.lcstate:
#            raise BadRequest("This deploment is already active")

        device_models, site_models = self.collect_deployment_components(deployment_id)

        # create a CSP so we can solve it
        problem = constraint.Problem()

        # add variables - the devices to be assigned, and their range (possible sites)
        for device_id in device_models.keys():
            device_model = device_models[device_id]
            possible_sites = [s for s in site_models.keys()
                              if device_model in site_models[s]]
                                    #and self.streamdef_of_site(s) in self.streamdefs_of_device(device_id)]
            problem.addVariable("device_%s" % device_id, possible_sites)

        # add the constraint that all the variables have to pick their own site
        problem.addConstraint(constraint.AllDifferentConstraint(),
            ["device_%s" % device_id for device_id in device_models.keys()])

        # perform CSP solve; this will be a list of solutions, each a dict of var -> value
        solutions = problem.getSolutions()

        if 1 > len(solutions):
            raise BadRequest("The set of devices could not be mapped to the set of sites, based on matching " +
                             "model and streamdefs")
        elif 1 < len(solutions):
            log.warn("Found %d possible ways to map device and site, but just picking the first one" % len(solutions))

        pairs_add = []
        pairs_rem = []

        for device_id in device_models.keys():
            site_id = solutions[0]["device_%s" % device_id]
            old_pair = self.check_site_device_pair_for_deployment(site_id, device_id)
            if old_pair:
                pairs_rem.append(old_pair)
            new_pair = (site_id, device_id)
            pairs_add.append(new_pair)

        # process any removals
        for site_id, device_id in pairs_rem:
            log.info("Unassigning hasDevice; device '%s' from site '%s'" % (device_id, site_id))
            if not activate_subscriptions:
                log.warn("The input to the data product for site '%s' will no longer come from its primary device" %
                         site_id)
            self.unassign_device_from_site(device_id, site_id)

        # process the additions
        for site_id, device_id in pairs_add:
            log.info("Setting primary device '%s' for site '%s'" % (device_id, site_id))
            self.assign_device_to_site(device_id, site_id)
            if activate_subscriptions:
                log.info("Activating subscription too")
                self.transfer_site_subscription(site_id)
#
#        self.RR.execute_lifecycle_transition(deployment_id, LCE.DEPLOY)


    def deactivate_deployment(self, deployment_id=''):
        """Remove the primary device designation for the deployed devices at the sites

        @param deployment_id    str
        @throws NotFound    object with specified id does not exist
        @throws BadRequest    if devices can not be undeployed
        """

        #Verify that the deployment exists
        deployment_obj = self.clients.resource_registry.read(deployment_id)

#        if LCS.DEPLOYED != deployment_obj.lcstate:
#            raise BadRequest("This deploment is not active")

        # get all associated components
        device_models, site_models = self.collect_deployment_components(deployment_id)

        #must only remove from sites that are not deployed under a different active deployment
        # must only remove devices that are not deployed under a different active deployment
        def filter_alternate_deployments(resource_list):
            # return the list of ids for devices or sites not connected to an alternate lcs.deployed deployment
            ret = []
            for r in resource_list:
                depls, _ = self.RR.find_objects(r, PRED.hasDeployment, RT.Deployment)
                keep = True
                for d in depls:
                    if d._id != deployment_id and LCS.DEPLOYED == d.lcstate:
                        keep = False
                if keep:
                    ret.append(r)
            return ret

        device_ids = filter_alternate_deployments(device_models.keys())
        site_ids   = filter_alternate_deployments(site_models.keys())

        # delete only associations where both site and device have passed the filter
        for s in site_ids:
            ds, _ = self.RR.find_objects(s, PRED.hasDevice, id_only=True)
            for d in ds:
                if d in device_ids:
                    a = self.RR.get_association(s, PRED.hasDevice, d)
                    self.RR.delete_association(a)
#
#        # mark deployment as not deployed (developed seems appropriate)
#        self.RR.execute_lifecycle_transition(deployment_id, LCE.DEVELOPED)



    def transfer_site_subscription(self, site_id=""):
        """
        Transfer the site subscription to the current hasDevice link
        """

        # get site obj
        site_obj = self.RR.read(site_id)

        # error if no hasDevice
        devices, _ = self.RR.find_objects(site_id, PRED.hasDevice, None, True)
        if 1 != len(devices):
            raise BadRequest("Expected 1 hasDevice association, got %d" % len(devices))
        device_id = devices[0]

        # get device obj
        device_obj = self.RR.read(device_id)

        # error if models don't match
        site_type = type(site_obj).__name__
        device_type = type(device_obj).__name__
        if RT.InstrumentDevice == device_type:
            model_type = RT.InstrumentModel
        elif RT.PlatformDevice == device_type:
            model_type = RT.PlatformModel
        else:
            raise BadRequest("Expected a device type, got '%s'" % device_type)

        device_model = self.check_device_for_deployment(device_id, device_type, model_type)
        site_models = self.check_site_for_deployment(site_id, site_type, model_type)
        if device_model not in site_models:
            raise BadRequest("The site and device model types are incompatible")

        # check site/device pair.
        # this function re-checks the association as a side effect, so this error should NEVER happen
        if self.check_site_device_pair_for_deployment(site_id, device_id, site_type, device_type):
            raise BadRequest("Magically and unfortunately, the site and device are no longer associated")

        # get deployments
        depl_site, _ = self.RR.find_objects(site_id, PRED.hasDeployment, RT.Deployment, True)
        depl_dev, _  = self.RR.find_objects(device_id, PRED.hasDeployment, RT.Deployment, True)

        # error if no matching deployments
        found = False
        for ds in depl_site:
            if ds in depl_dev:
                found = True
                break
        if not found:
            raise BadRequest("Site and device do not share a deployment!")

        # check product and process from site
        pduct_ids, _ = self.RR.find_objects(site_id, PRED.hasOutputProduct, RT.DataProduct, True)
        if 1 != len(pduct_ids):
            raise BadRequest("Expected 1 DataProduct associated to site '%s' but found %d" % (site_id, len(pduct_ids)))
        process_ids, _ = self.RR.find_subjects(RT.DataProcess, PRED.hasOutputProduct, pduct_ids[0], True)
        if 1 != len(process_ids):
            raise BadRequest("Expected 1 DataProcess feeding DataProduct '%s', but found %d" %
                             (pduct_ids[0], len(process_ids)))

        #look up stream defs
        ss = self.streamdef_of_site(site_id)
        ds = self.streamdefs_of_device(device_id)

        if not ss in ds:
            raise BadRequest("Data product(s) of site does not have any matching streamdef for data product of device")

        data_process_id = process_ids[0]
        log.info("Changing subscription")
        self.PRMS.update_data_process_inputs(data_process_id, [ds[ss]])





    ##########################################################################
    #
    # FIND OPS
    #
    ##########################################################################



    def find_org_by_observatory(self, observatory_id=''):
        """

        """
        orgs,_ = self.RR.find_subjects(RT.Org, PRED.hasResource, observatory_id, id_only=False)
        return orgs



    def find_related_frames_of_reference(self, input_resource_id='', output_resource_type_list=None):

        # the relative depth of each resource type in our tree
        depth = self.HIERARCHY_DEPTH

        input_obj  = self.RR.read(input_resource_id)
        input_type = input_obj._get_type()

        #input type checking
        if not input_type in depth:
            raise BadRequest("Input resource type (got %s) must be one of %s" % 
                             (input_type, self.HIERARCHY_LOOKUP))
        for t in output_resource_type_list:
            if not t in depth:
                raise BadRequest("Output resource types (got %s) must be one of %s" %
                                 (str(output_resource_type_list), self.HIERARCHY_LOOKUP))

                             

        subordinates = [x for x in output_resource_type_list if depth[x] > depth[input_type]]
        superiors    = [x for x in output_resource_type_list if depth[x] < depth[input_type]]

        acc = {}
        acc[input_type] = [input_obj]


        if subordinates:
            # figure out the actual depth we need to go
            deepest_type = input_type #initial value
            for output_type in output_resource_type_list:
                if depth[deepest_type] < depth[output_type]:
                    deepest_type = output_type

            log.debug("Deepest level for search will be '%s'" % deepest_type)

            acc = self._traverse_entity_tree(acc, input_type, deepest_type, True)


        if superiors:
            highest_type = input_type #initial value

            for output_type in output_resource_type_list:
                if depth[highest_type] > depth[output_type]:
                    highest_type = output_type

            log.debug("Highest level for search will be '%s'" % highest_type)

            acc = self._traverse_entity_tree(acc, highest_type, input_type, False)

        # Don't include input type in response            
        #TODO: maybe just remove the input resource id 
        if input_type in acc:
            stripped = []
            for r_obj in acc[input_type]:
                if r_obj._id == input_resource_id:
                    log.debug("Stripped input from return value")
                else:
                    stripped.append(r_obj)

            acc[input_type] = stripped
                

        return acc
                    

    def _traverse_entity_tree(self, acc, top_type, bottom_type, downward):

        call_list = self._build_call_list(top_type, bottom_type, downward)

        # start calling functions
        if downward:
            for (p, c) in call_list:
                acc = self._find_subordinate(acc, p, c)
        else:
            for (p, c) in call_list:
                acc = self._find_superior(acc, p, c)

        return acc


    def _build_call_list(self, top_type, bottom_type, downward):

        call_list = []

        if downward:
            step = 1
            top_ord = self.HIERARCHY_DEPTH[top_type]
            bot_ord = self.HIERARCHY_DEPTH[bottom_type]
        else:
            step = -1
            top_ord = self.HIERARCHY_DEPTH[bottom_type] - 1
            bot_ord = self.HIERARCHY_DEPTH[top_type] - 1


        for i in range(top_ord, bot_ord, step):
            child  = self.HIERARCHY_LOOKUP[i + 1]
            parent = self.HIERARCHY_LOOKUP[i]
            if downward:
                tmp = [(parent, parent), (parent, child), (child, child)]
            else:
                tmp = [(child, child), (parent, child), (parent, parent)]


            for pair in tmp:
                if not pair in call_list:
                    #log.debug("adding %s" % str(pair))
                    call_list.append(pair)

        return call_list



    def _find_subordinate(self, acc, parent_type, child_type):
        #acc is an accumulated dictionary

        find_fns = {
            (RT.Observatory, RT.Subsite):         self.observatory.find_stemming_site,
            (RT.Subsite, RT.Subsite):             self.subsite.find_stemming_subsite,
            (RT.Subsite, RT.PlatformSite):        self.subsite.find_stemming_platform_site,
            (RT.PlatformSite, RT.PlatformSite):   self.platform_site.find_stemming_platform_site,
            (RT.PlatformSite, RT.InstrumentSite): self.platform_site.find_stemming_instrument_site,
            }

        if (parent_type, child_type) in find_fns:
            find_fn = find_fns[(parent_type, child_type)]
        else:
            find_fn = (lambda x : [])
        
        if not parent_type in acc: acc[parent_type] = []

        log.debug("Subordinates: '%s'x%d->'%s'" % (parent_type, len(acc[parent_type]), child_type))

        #for all parents in the acc, add all their children
        for parent_obj in acc[parent_type]:
            parent_id = parent_obj._id
            for child_obj in find_fn(parent_id):
                actual_child_type = child_obj._get_type()
                if not actual_child_type in acc:
                    acc[actual_child_type] = []
                acc[actual_child_type].append(child_obj)

        return acc



    def _find_superior(self, acc, parent_type, child_type):
        # acc is an accumualted dictionary

        #log.debug("Superiors: '%s'->'%s'" % (parent_type, child_type))
        #if True:
        #    return acc
            
        find_fns = {
            (RT.Observatory, RT.Subsite):         self.observatory.find_having_site,
            (RT.Subsite, RT.Subsite):             self.subsite.find_having_site,
            (RT.Subsite, RT.PlatformSite):        self.subsite.find_having_site,
            (RT.PlatformSite, RT.PlatformSite):   self.platform_site.find_having_site,
            (RT.PlatformSite, RT.InstrumentSite): self.platform_site.find_having_site,
            }

        if (parent_type, child_type) in find_fns:
            find_fn = find_fns[(parent_type, child_type)]
        else:
            find_fn = (lambda x : [])
        
        if not child_type in acc: acc[child_type] = []

        log.debug("Superiors: '%s'->'%s'x%d" % (parent_type, child_type, len(acc[child_type])))

        #for all children in the acc, add all their parents
        for child_obj in acc[child_type]:
            child_id = child_obj._id
            for parent_obj in find_fn(child_id):
                actual_parent_type = parent_obj._get_type()
                if not actual_parent_type in acc:
                    acc[actual_parent_type] = []
                acc[actual_parent_type].append(parent_obj)

        return acc



    ############################
    #
    #  EXTENDED RESOURCES
    #
    ############################


    def get_observatory_extension(self, observatory_id='', ext_associations=None, ext_exclude=None):
        """Returns an InstrumentDeviceExtension object containing additional related information

        @param observatory_id    str
        @param ext_associations    dict
        @param ext_exclude    list
        @retval observatory    ObservatoryExtension
        @throws BadRequest    A parameter is missing
        @throws NotFound    An object with the specified observatory_id does not exist
        """

        if not observatory_id:
            raise BadRequest("The observatory_id parameter is empty")

        extended_resource_handler = ExtendedResourceContainer(self)

        extended_instrument = extended_resource_handler.create_extended_resource_container(
            OT.ObservatoryExtension,
            observatory_id,
            OT.ObservatoryComputedAttributes,
            ext_associations,
            ext_exclude)

        #Loop through any attachments and remove the actual content since we don't need to send it to the front end this way
        #TODO - see if there is a better way to do this in the extended resource frame work.
        if hasattr(extended_instrument, 'attachments'):
            for att in extended_instrument.attachments:
                if hasattr(att, 'content'):
                    delattr(att, 'content')

        return extended_instrument


        #Bogus functions for computed attributes
    def get_number_data_sets(self, observatory_id):
        return "1"

    def get_number_instruments_deployed(self, observatory_id):
        return "1"


    def get_number_instruments_operational(self, observatory_id):
        return "1"


    def get_number_instruments_inoperational(self, observatory_id):
        return "1"  


    def get_number_instruments(self, observatory_id):
        return "1"


    def get_number_platforms(self, observatory_id):
        return "1"

    def get_number_platforms_deployed(self, observatory_id):
        return "1"