Move disco_get_info and disco_get_items to AccountManager

discovery get_info and get_items handler now use a generic
method "apply_behavior" to determine receiver (main component,
account type node or account).
AccountManager split previous handler in a more testable way
(test returned objects instead of XML).

darcs-hash:20070327163756-86b55-3932d4b1f59fb20d6aa8e096cdf06049095a4c2b.gz
This commit is contained in:
David Rousselie
2007-03-27 18:37:56 +02:00
parent 0fad164e57
commit 516bb75a77
3 changed files with 199 additions and 89 deletions

View File

@@ -58,9 +58,9 @@ if __name__ == '__main__':
jcl_suite = unittest.TestSuite() jcl_suite = unittest.TestSuite()
# jcl_suite.addTest(FeederComponent_TestCase('test_handle_tick')) # jcl_suite.addTest(FeederComponent_TestCase('test_handle_tick'))
# jcl_suite.addTest(JCLComponent_TestCase('test_handle_get_register_exist_complex')) # jcl_suite.addTest(JCLComponent_TestCase('test_disco_get_items_2types_no_node'))
# jcl_suite.addTest(PresenceAccount_TestCase('test_possibles_actions')) # jcl_suite.addTest(PresenceAccount_TestCase('test_possibles_actions'))
# jcl_suite = unittest.TestSuite((account_module_suite)) # jcl_suite = unittest.TestSuite((component_suite))
# jcl_suite = unittest.TestSuite((presence_account_suite)) # jcl_suite = unittest.TestSuite((presence_account_suite))
jcl_suite = unittest.TestSuite((component_suite, \ jcl_suite = unittest.TestSuite((component_suite, \

View File

@@ -90,11 +90,10 @@ class JCLComponent(Component, object):
self.name = "Jabber Component Library generic component" self.name = "Jabber Component Library generic component"
self.spool_dir = "." self.spool_dir = "."
self.db_connection_str = db_connection_str self.db_connection_str = db_connection_str
self.account_classes = [Account]
self.version = VERSION self.version = VERSION
self.accounts = []
self.time_unit = 60 self.time_unit = 60
self.queue = Queue(100) self.queue = Queue(100)
self.account_manager = AccountManager(self)
self.__logger = logging.getLogger("jcl.jabber.JCLComponent") self.__logger = logging.getLogger("jcl.jabber.JCLComponent")
self.lang = lang self.lang = lang
@@ -104,6 +103,9 @@ class JCLComponent(Component, object):
signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGINT, self.signal_handler)
signal.signal(signal.SIGTERM, self.signal_handler) signal.signal(signal.SIGTERM, self.signal_handler)
# TODO : delete
self.account_classes = (Account,)
def run(self): def run(self):
"""Main loop """Main loop
Connect to Jabber server Connect to Jabber server
@@ -242,68 +244,52 @@ class JCLComponent(Component, object):
self.__logger.debug("Signal %i received, shutting down..." % (signum,)) self.__logger.debug("Signal %i received, shutting down..." % (signum,))
self.running = False self.running = False
def apply_behavior(self, info_query, \
account_handler, \
account_type_handler, \
root_handler, \
send_result = False):
bare_from_jid = info_query.get_from().bare()
to_jid = info_query.get_to()
name = to_jid.node
account_type = to_jid.resource
if name is not None: # account
self.__logger.debug("Applying behavior on account " + name)
result = account_handler(name, bare_from_jid, account_type)
elif account_type is None: # root
self.__logger.debug("Applying behavior on root node")
result = root_handler(name, bare_from_jid, account_type)
else: # account type
self.__logger.debug("Applying behavior on account type " + account_type)
result = account_type_handler(name, bare_from_jid, account_type)
if send_result:
self._logger.debug("Sending responses")
for stanza in result:
self.stream.send(stanza)
return result
def disco_get_info(self, node, info_query): def disco_get_info(self, node, info_query):
"""Discovery get info handler """Discovery get info handler
""" """
self.__logger.debug("DISCO_GET_INFO") return self.apply_behavior(info_query, \
disco_info = DiscoInfo() lambda name, bare_from_jid, account_type: \
if node is not None: self.account_manager.account_disco_get_info(), \
disco_info.add_feature("jabber:iq:register") lambda name, bare_from_jid, account_type: \
else: self.account_manager.account_type_disco_get_info, \
disco_info.add_feature("jabber:iq:version") lambda name, bare_from_jid, account_type: \
if len(self.account_classes) == 1: self.account_manager.root_disco_get_info(self.name, \
disco_info.add_feature("jabber:iq:register") self.disco_identity.category, \
DiscoIdentity(disco_info, self.name, self.disco_identity.type))
self.disco_identity.category,
self.disco_identity.type)
return disco_info
def disco_get_items(self, node, info_query): def disco_get_items(self, node, info_query):
"""Discovery get nested nodes handler """Discovery get nested nodes handler
""" """
self.__logger.debug("DISCO_GET_ITEMS") return self.apply_behavior(info_query, \
base_from_jid = unicode(info_query.get_from().bare()) lambda name, bare_from_jid, account_type: DiscoItems(), \
disco_items = DiscoItems() lambda name, bare_from_jid, account_type: \
if not node: # first level self.account_manager.account_type_disco_get_items(bare_from_jid, account_type), \
if len(self.account_classes) == 1: # list accounts with only one type declared lambda name, bare_from_jid, account_type:
regexp_type = re.compile("(.*)Account$") self.account_manager.root_disco_get_items(bare_from_jid))
match = regexp_type.search(self.account_classes[0].__name__)
if match is not None:
account_type = match.group(1)
self._list_accounts(disco_items, self.account_classes[0], \
base_from_jid, account_type)
else:
self.__logger.error(self.account_classes[0].__name__ + \
" name not well formed")
else: # list account types (when multiples accounts types)
for account_class in self.account_classes:
regexp_type = re.compile("(.*)Account$")
match = regexp_type.search(account_class.__name__)
if match is not None:
account_type = match.group(1)
item_jid = JID(unicode(self.jid) + "/" + account_type)
DiscoItem(disco_items, \
item_jid, \
account_type, \
account_type)
else:
self.__logger.error(account_class.__name__ + \
" name not well formed")
else: # second level
nodes = node.split("/");
if len(nodes) == 1 \
and len(self.account_classes) > 1: # second level only with multiples account types
self.__logger.debug("Listing account for " + nodes[0])
account_class = self._get_account_class(nodes[0] + "Account")
if account_class is not None:
self._list_accounts(disco_items, \
account_class, \
base_from_jid,
account_type = nodes[0])
else:
self.__logger.error("Error: " + account_class.__name__ \
+ " class not in account_classes")
return disco_items
def handle_get_version(self, info_query): def handle_get_version(self, info_query):
"""Get Version handler """Get Version handler
@@ -661,6 +647,7 @@ class JCLComponent(Component, object):
########################################################################### ###########################################################################
# Utils # Utils
########################################################################### ###########################################################################
# TODO : delete
def _get_account_class(self, account_class_name): def _get_account_class(self, account_class_name):
"""Return account class definition from declared classes in """Return account class definition from declared classes in
account_classes from its class name""" account_classes from its class name"""
@@ -672,23 +659,6 @@ class JCLComponent(Component, object):
self.__logger.debug(account_class_name + " not found") self.__logger.debug(account_class_name + " not found")
return None return None
def _list_accounts(self, disco_items, _account_class, base_from_jid, account_type = ""):
"""List accounts in disco_items for given _account_class and user jid"""
if account_type is not None and account_type != "":
resource = "/" + account_type
account_type = account_type + "/"
else:
resource = ""
self.db_connect()
for _account in _account_class.select(_account_class.q.user_jid == \
base_from_jid):
self.__logger.debug(str(_account))
DiscoItem(disco_items, \
JID(unicode(_account.jid) + resource), \
account_type + _account.name, \
_account.long_name)
self.db_disconnect()
def _send_presence_available(self, _account, show, lang_class): def _send_presence_available(self, _account, show, lang_class):
"""Send available presence to account's user and ask for password """Send available presence to account's user and ask for password
if necessary""" if necessary"""
@@ -816,3 +786,129 @@ class JCLComponent(Component, object):
Called regularly Called regularly
""" """
raise NotImplementedError raise NotImplementedError
class AccountManager(object):
"""Implement component account behavior"""
def __init__(self, component):
"""AccountManager constructor"""
self.__logger = logging.getLogger("jcl.jabber.JCLComponent")
self.account_classes = (Account,)
self.component = component
def _get_account_classes(self):
"""account_classes getter"""
return self._account_classes
def _set_account_classes(self, account_classes):
"""account_classes setter"""
self._account_classes = account_classes
self.has_multiple_account_type = (len(self._account_classes) > 1)
account_classes = property(_get_account_classes, _set_account_classes)
###### disco_get_info handlers ######
def account_disco_get_info(self):
"""Implement discovery get_info on an account node"""
self.__logger.debug("account_disco_get_info")
disco_info = DiscoInfo()
disco_info.add_feature("jabber:iq:register")
return disco_info
def account_type_disco_get_info(self):
"""Implement discovery get_info on an account type node"""
self.__logger.debug("account_type_disco_get_info")
return self.account_disco_get_info()
def root_disco_get_info(self, name, category, type):
"""Implement discovery get_info on main component JID"""
self.__logger.debug("root_disco_get_info")
disco_info = DiscoInfo()
disco_info.add_feature("jabber:iq:version")
if not self.has_multiple_account_type:
disco_info.add_feature("jabber:iq:register")
DiscoIdentity(disco_info, name, \
category, \
type)
return disco_info
###### disco_get_items handlers ######
def account_type_disco_get_items(self, bare_from_jid, account_type):
"""Discovery get_items on an account type node"""
self.__logger.debug("Listing account for " + account_type)
disco_items = DiscoItems()
account_class = self._get_account_class(account_type + "Account")
if account_class is not None:
self._list_accounts(disco_items, \
account_class, \
bare_from_jid, \
account_type = account_type)
else:
self.__logger.error("Error: " + account_class.__name__ \
+ " class not in account_classes")
return disco_items
def root_disco_get_items(self, bare_from_jid):
"""Discovery get_items on root node"""
disco_items = DiscoItems()
regexp_type = re.compile("(.*)Account$")
if self.has_multiple_account_type: # list accounts with only one type declared
list_func = lambda disco_items, account_class, bare_from_jid, account_type: \
DiscoItem(disco_items, \
JID(unicode(self.component.jid) + "/" + account_type), \
account_type, \
account_type)
else:
list_func = self._list_accounts
for account_class in self.account_classes:
match = regexp_type.search(account_class.__name__)
if match is not None:
account_type = match.group(1)
list_func(disco_items, account_class, \
bare_from_jid, account_type)
else:
self.__logger.error(account_class.__name__ + \
" name not well formed")
return disco_items
###### Utils methods ######
def _list_accounts(self, disco_items, _account_class, bare_from_jid, account_type = ""):
"""List accounts in disco_items for given _account_class and user jid"""
if account_type is not None and account_type != "":
resource = "/" + account_type
account_type = account_type + "/"
else:
resource = ""
self.db_connect()
for _account in _account_class.select(_account_class.q.user_jid == \
unicode(bare_from_jid)):
self.__logger.debug(str(_account))
DiscoItem(disco_items, \
JID(unicode(_account.jid) + resource), \
account_type + _account.name, \
_account.long_name)
self.db_disconnect()
def _get_account_class(self, account_class_name):
"""Return account class definition from declared classes in
account_classes from its class name"""
self.__logger.debug("Looking for " + account_class_name)
for _account_class in self.account_classes:
if _account_class.__name__.lower() == account_class_name.lower():
self.__logger.debug(account_class_name + " found")
return _account_class
self.__logger.debug(account_class_name + " not found")
return None
def db_connect(self):
"""Create a new connection to the DataBase (SQLObject use connection
pool) associated to the current thread"""
account.hub.threadConnection = \
connectionForURI(self.component.db_connection_str) # TODO : move db_connection_str to AccountManager
# account.hub.threadConnection.debug = True
def db_disconnect(self):
"""Delete connection associated to the current thread"""
del account.hub.threadConnection

View File

@@ -352,25 +352,39 @@ class JCLComponent_TestCase(unittest.TestCase):
# 'disco_get_info' tests # 'disco_get_info' tests
########################################################################### ###########################################################################
def test_disco_get_info(self): def test_disco_get_info(self):
disco_info = self.comp.disco_get_info(None, None) info_query = Iq(stanza_type = "get", \
from_jid = "user1@test.com", \
to_jid = "jcl.test.com")
disco_info = self.comp.disco_get_info(None, info_query)
self.assertEquals(disco_info.get_identities()[0].get_name(), self.comp.name) self.assertEquals(disco_info.get_identities()[0].get_name(), self.comp.name)
self.assertTrue(disco_info.has_feature("jabber:iq:version")) self.assertTrue(disco_info.has_feature("jabber:iq:version"))
self.assertTrue(disco_info.has_feature("jabber:iq:register")) self.assertTrue(disco_info.has_feature("jabber:iq:register"))
def test_disco_get_info_multiple_account_type(self): def test_disco_get_info_multiple_account_type(self):
self.comp.account_classes = [ExampleAccount, Example2Account] self.comp.account_manager.account_classes = (ExampleAccount, Example2Account)
disco_info = self.comp.disco_get_info(None, None) info_query = Iq(stanza_type = "get", \
self.assertEquals(disco_info.get_identities()[0].get_name(), self.comp.name) from_jid = "user1@test.com", \
to_jid = "jcl.test.com")
disco_info = self.comp.disco_get_info(None, info_query)
self.assertEquals(disco_info.get_identities()[0].get_name(), \
self.comp.name)
self.assertTrue(disco_info.has_feature("jabber:iq:version")) self.assertTrue(disco_info.has_feature("jabber:iq:version"))
self.assertFalse(disco_info.has_feature("jabber:iq:register")) self.assertFalse(disco_info.has_feature("jabber:iq:register"))
def test_disco_get_info_node(self): def test_disco_get_info_node(self):
disco_info = self.comp.disco_get_info("node_test", None) info_query = Iq(stanza_type = "get", \
from_jid = "user1@test.com", \
to_jid = "node_test@jcl.test.com")
disco_info = self.comp.disco_get_info("node_test", info_query)
self.assertTrue(disco_info.has_feature("jabber:iq:register")) self.assertTrue(disco_info.has_feature("jabber:iq:register"))
def test_disco_get_info_long_node(self): def test_disco_get_info_long_node(self):
self.comp.account_classes = [ExampleAccount, Example2Account] self.comp.account_manager.account_classes = (ExampleAccount, Example2Account)
disco_info = self.comp.disco_get_info("node_type/node_test", None) info_query = Iq(stanza_type = "get", \
from_jid = "user1@test.com", \
to_jid = "node_test@jcl.test.com/node_type")
disco_info = self.comp.disco_get_info("node_type/node_test", \
info_query)
self.assertTrue(disco_info.has_feature("jabber:iq:register")) self.assertTrue(disco_info.has_feature("jabber:iq:register"))
########################################################################### ###########################################################################
@@ -408,7 +422,7 @@ class JCLComponent_TestCase(unittest.TestCase):
def test_disco_get_items_2types_no_node(self): def test_disco_get_items_2types_no_node(self):
"""get_items on main entity. Must account types""" """get_items on main entity. Must account types"""
self.comp.account_classes = [ExampleAccount, Example2Account] self.comp.account_manager.account_classes = (ExampleAccount, Example2Account)
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account11 = ExampleAccount(user_jid = "user1@test.com", \ account11 = ExampleAccount(user_jid = "user1@test.com", \
name = "account11", \ name = "account11", \
@@ -436,7 +450,7 @@ class JCLComponent_TestCase(unittest.TestCase):
def test_disco_get_items_2types_with_node(self): def test_disco_get_items_2types_with_node(self):
"""get_items on the first account type node. Must return account list of """get_items on the first account type node. Must return account list of
that type for the current user""" that type for the current user"""
self.comp.account_classes = [ExampleAccount, Example2Account] self.comp.account_manager.account_classes = (ExampleAccount, Example2Account)
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account11 = ExampleAccount(user_jid = "user1@test.com", \ account11 = ExampleAccount(user_jid = "user1@test.com", \
name = "account11", \ name = "account11", \
@@ -464,7 +478,7 @@ class JCLComponent_TestCase(unittest.TestCase):
def test_disco_get_items_2types_with_node2(self): def test_disco_get_items_2types_with_node2(self):
"""get_items on the second account type node. Must return account list of """get_items on the second account type node. Must return account list of
that type for the current user""" that type for the current user"""
self.comp.account_classes = [ExampleAccount, Example2Account] self.comp.account_manager.account_classes = (ExampleAccount, Example2Account)
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account11 = ExampleAccount(user_jid = "user1@test.com", \ account11 = ExampleAccount(user_jid = "user1@test.com", \
name = "account11", \ name = "account11", \
@@ -491,7 +505,7 @@ class JCLComponent_TestCase(unittest.TestCase):
def test_disco_get_items_2types_with_long_node(self): def test_disco_get_items_2types_with_long_node(self):
"""get_items on a first type account. Must return nothing""" """get_items on a first type account. Must return nothing"""
self.comp.account_classes = [ExampleAccount, Example2Account] self.comp.account_manager.account_classes = (ExampleAccount, Example2Account)
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account1 = ExampleAccount(user_jid = "user1@test.com", \ account1 = ExampleAccount(user_jid = "user1@test.com", \
name = "account1", \ name = "account1", \
@@ -505,7 +519,7 @@ class JCLComponent_TestCase(unittest.TestCase):
def test_disco_get_items_2types_with_long_node2(self): def test_disco_get_items_2types_with_long_node2(self):
"""get_items on a second type account. Must return nothing""" """get_items on a second type account. Must return nothing"""
self.comp.account_classes = [ExampleAccount, Example2Account] self.comp.account_manager.account_classes = (ExampleAccount, Example2Account)
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account1 = Example2Account(user_jid = "user1@test.com", \ account1 = Example2Account(user_jid = "user1@test.com", \
name = "account1", \ name = "account1", \