Commit a2341479 authored by Aurel's avatar Aurel

new version of ERP5SyncML using ERP5 Objects


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@43048 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent f30cd399
......@@ -28,7 +28,7 @@
##############################################################################
from Products.ERP5SyncML.XMLSyncUtils import XMLSyncUtilsMixin
from Products.ERP5SyncML.Conflict import Conflict
from Products.ERP5SyncML.XMLSyncUtils import getXupdateObject
from Products.ERP5Type.Utils import deprecated
from Products.ERP5Type.XMLExportImport import MARSHALLER_NAMESPACE_URI
from Products.CMFCore.utils import getToolByName
......@@ -51,6 +51,38 @@ from base64 import standard_b64decode
from zope.interface import implements
from copy import deepcopy
from Products.ERP5SyncML.SyncMLConstant import XUPDATE_ELEMENT,\
XUPDATE_INSERT_OR_ADD_LIST, XUPDATE_DEL, XUPDATE_UPDATE, XUPDATE_INSERT_LIST
# Constant
HISTORY_TAG = 'workflow_action'
XML_OBJECT_TAG = 'object'
LOCAL_ROLE_TAG = 'local_role'
LOCAL_PERMISSION_TAG = 'local_permission'
LOCAL_GROUP_TAG = 'local_group'
LOCAL_ROLE_LIST = (LOCAL_ROLE_TAG, LOCAL_GROUP_TAG,)
ADDABLE_PROPERTY_LIST = LOCAL_ROLE_LIST + (HISTORY_TAG,) + (LOCAL_PERMISSION_TAG,)
NOT_EDITABLE_PROPERTY_LIST = ('id', 'object', 'uid', 'attribute::type',) +\
(XUPDATE_ELEMENT,) + ADDABLE_PROPERTY_LIST
FORCE_CONFLICT_LIST = ('layout_and_schema', 'ModificationDate')
from Products.ERP5Type.Accessor.TypeDefinition import list_types
LIST_TYPE_LIST = list_types
TEXT_TYPE_LIST = ('text', 'string',)
NONE_TYPE = 'None'
DATE_TYPE = 'date'
DICT_TYPE = 'dict'
INT_TYPE = 'int'
DATA_TYPE_LIST = ('data', 'object',)
BOOLEAN_TYPE = 'boolean'
HISTORY_EXP = re.compile("/%s\[@id='.*'\]" % HISTORY_TAG)
BAD_HISTORY_EXP = re.compile("/%s\[@id='.*'\]/" % HISTORY_TAG)
EXTRACT_ID_FROM_XPATH = re.compile(
"(?P<object_block>(?P<property>[^/]+)\[@"\
"(?P<id_of_id>id|gid)='(?P<object_id>[^']+)'\])")
class ERP5Conduit(XMLSyncUtilsMixin):
"""
A conduit is a piece of code in charge of
......@@ -58,9 +90,9 @@ class ERP5Conduit(XMLSyncUtilsMixin):
- updating an object attributes from an XUpdate XML stream
(Conduits are not in charge of creating new objects which
are eventually missing in a synchronisation process)
are eventually missing in a synchronization process)
If an object has be created during a synchronisation process,
If an object has be created during a synchronization process,
the way to proceed consists in:
1- creating an empty instance of the appropriate class
......@@ -68,9 +100,9 @@ class ERP5Conduit(XMLSyncUtilsMixin):
2- updating that empty instance with the conduit
The first implementation of ERP5 synchronisation
The first implementation of ERP5 synchronization
will define a default location to create new objects and
a default class. This will be defined at the level of the synchronisation
a default class. This will be defined at the level of the synchronization
tool
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
......@@ -102,9 +134,9 @@ class ERP5Conduit(XMLSyncUtilsMixin):
#return "iso-8859-1"
return "utf-8"
security.declareProtected(Permissions.ModifyPortalContent, '__init__')
def __init__(self):
self.args = {}
#security.declareProtected(Permissions.ModifyPortalContent, '__init__')
#def __init__(self):
#self.args = {}
security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
def addNode(self, xml=None, object=None, sub_object=None, reset=None,
......@@ -128,25 +160,25 @@ class ERP5Conduit(XMLSyncUtilsMixin):
reset_workflow = False
conflict_list = []
xml = self.convertToXml(xml)
#LOG('ERP5Conduit.addNode', INFO, 'object path:%s' % object.getPath())
#LOG('ERP5Conduit.addNode', INFO, 'object path:%r' % object.getPath())
#LOG('ERP5Conduit.addNode', INFO, '\n%s' % etree.tostring(xml, pretty_print=True))
if xml is None:
return {'conflict_list': conflict_list, 'object': sub_object}
# In the case where this new node is a object to add
xpath_expression = xml.get('select')
if xml.xpath('local-name()') == self.history_tag and not reset:
if xml.xpath('local-name()') == HISTORY_TAG and not reset:
conflict_list += self.addWorkflowNode(object, xml, simulate)
elif xml.xpath('name()') in self.XUPDATE_INSERT_OR_ADD and\
elif xml.xpath('name()') in XUPDATE_INSERT_OR_ADD_LIST and\
MARSHALLER_NAMESPACE_URI not in xml.nsmap.values():
# change the context according select expression
get_target_parent = xml.xpath('name()') in self.XUPDATE_INSERT
get_target_parent = xml.xpath('name()') in XUPDATE_INSERT_LIST
context = self.getContextFromXpath(object, xpath_expression,
get_target_parent=get_target_parent)
for element in xml.findall('{%s}element' % xml.nsmap['xupdate']):
xml = self.getElementFromXupdate(element)
conflict_list += self.addNode(xml=xml, object=context, **kw)\
['conflict_list']
elif xml.xpath('local-name()') == self.xml_object_tag:
elif xml.xpath('local-name()') == XML_OBJECT_TAG:
sub_object = self._createContent(xml=xml,
object=object,
sub_object=sub_object,
......@@ -155,9 +187,9 @@ class ERP5Conduit(XMLSyncUtilsMixin):
reset=reset,
simulate=simulate,
**kw)
elif xml.xpath('local-name()') in self.local_role_list:
elif xml.xpath('local-name()') in LOCAL_ROLE_LIST:
self.addLocalRoleNode(object, xml)
elif xml.xpath('local-name()') in self.local_permission_list:
elif xml.xpath('local-name()') == LOCAL_PERMISSION_TAG:
conflict_list += self.addLocalPermissionNode(object, xml)
else:
conflict_list += self.updateNode(xml=xml, object=object, reset=reset,
......@@ -178,22 +210,26 @@ class ERP5Conduit(XMLSyncUtilsMixin):
xml = self.convertToXml(xml)
#LOG('ERP5Conduit deleteNode', INFO, etree.tostring(xml, pretty_print=True))
xpath_expression = xml.get('select')
#LOG('ERP5Conduit xpath_expression', INFO, xpath_expression)
context_to_delete = self.getContextFromXpath(object, xpath_expression)
if context_to_delete != object:
if 'workflow_action' in xpath_expression:
# Something like /erp5/object[@gid='313730']/object[@id='170']/workflow_action[@id='edit_workflow'][2]
# we can not edit or erase workflow history
pass
elif context_to_delete != object:
self._deleteContent(object=context_to_delete.getParentValue(),
object_id=context_to_delete.getId())
else:
#same context
if [role for role in self.local_role_list if role in xpath_expression]:
user = self.extract_id_from_xpath.findall(xpath_expression)[-1][3]
if [role for role in LOCAL_ROLE_LIST if role in xpath_expression]:
user = EXTRACT_ID_FROM_XPATH.findall(xpath_expression)[-1][3]
#LOG('ERP5Conduit.deleteNode local_role: ', INFO, 'user: %r' % user)
if self.local_role_tag in xpath_expression:
if LOCAL_ROLE_TAG in xpath_expression:
object.manage_delLocalRoles([user])
elif self.local_group_tag in xpath_expression:
elif LOCAL_GROUP_TAG in xpath_expression:
object.manage_delLocalGroupRoles([user])
if [permission for permission in self.local_permission_list if\
permission in xpath_expression]:
permission = self.extract_id_from_xpath.findall(xpath_expression)[-1][3]
if LOCAL_PERMISSION_TAG in xpath_expression:
permission = EXTRACT_ID_FROM_XPATH.findall(xpath_expression)[-1][3]
#LOG('ERP5Conduit.deleteNode permission: ', INFO,
#'permission: %r' % permission)
object.manage_setLocalPermissions(permission)
......@@ -249,9 +285,9 @@ class ERP5Conduit(XMLSyncUtilsMixin):
select_list = new_select_list # Something like : ('','object','sid')
keyword = select_list[-1] # this will be 'sid'
data = None
if xml.xpath('name()') not in self.XUPDATE_INSERT_OR_ADD:
if xml.xpath('name()') not in XUPDATE_INSERT_OR_ADD_LIST:
for subnode in xml:
if subnode.xpath('name()') in self.XUPDATE_ELEMENT:
if subnode.xpath('name()') == XUPDATE_ELEMENT:
keyword = subnode.get('name')
data_xml = subnode
else:
......@@ -264,7 +300,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
reset=reset,
**kw)
return conflict_list
if xml.xpath('name()') in self.XUPDATE_DEL:
if xml.xpath('name()') in XUPDATE_DEL:
conflict_list += self.deleteNode(xml=xml,
object=object,
force=force,
......@@ -274,10 +310,10 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return conflict_list
if keyword is None: # This is not a selection, directly the property
keyword = xml.xpath('name()')
if keyword not in self.NOT_EDITABLE_PROPERTY:
if keyword not in NOT_EDITABLE_PROPERTY_LIST:
# We will look for the data to enter
xpath_expression = xml.get('select', xpath_expression)
get_target_parent = xml.xpath('name()') in self.XUPDATE_INSERT
get_target_parent = xml.xpath('name()') in XUPDATE_INSERT_LIST
context = self.getContextFromXpath(object,
xpath_expression,
get_target_parent=get_target_parent)
......@@ -293,7 +329,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
#last synchronization
# - current_data : the data actually on this box
isConflict = False
if previous_xml is not None and not force:
if previous_xml and not force:
# if no previous_xml, no conflict
#old_data = self.getObjectProperty(keyword, previous_xml,
#data_type=data_type)
......@@ -309,22 +345,25 @@ class ERP5Conduit(XMLSyncUtilsMixin):
#LOG('ERP5Conduit.updateNode', INFO, 'Conflict data: %s' % str(data))
#LOG('ERP5Conduit.updateNode', INFO, 'Conflict old_data: %s' % str(old_data))
#LOG('ERP5Conduit.updateNode', INFO, 'Conflict current_data: %s' % str(current_data))
if (old_data != current_data) and (data != current_data) \
and keyword not in self.force_conflict_list:
if (old_data != current_data) and (data != current_data) and\
keyword not in FORCE_CONFLICT_LIST:
#LOG('ERP5Conduit.updateNode', INFO, 'Conflict on : %s' % keyword)
# This is a conflict
isConflict = True
xml_string = etree.tostring(xml, encoding='utf-8')
conflict = Conflict(object_path=context.getPhysicalPath(),
keyword=keyword)
conflict.setXupdate(xml_string)
if not (data_type in self.binary_type_list):
conflict.setLocalValue(current_data)
conflict.setRemoteValue(data)
# unattach diff from its parent once copy has been
# stored on signature
xml.getparent().remove(xml)
conflict = kw['signature'].newContent(portal_type='SyncML Conflict',
origin_value=context,
property_id=keyword,
diff_chunk=xml_string)
if data_type not in DATA_TYPE_LIST:
conflict.edit(local_value=current_data,
remote_value=data)
conflict_list += [conflict]
# We will now apply the argument with the method edit
if args and (not isConflict or force) and \
(not simulate or reset):
if args and (not isConflict or force) and (not simulate or reset):
self._updateContent(object=context, **args)
# It is sometimes required to do something after an edit
if getattr(context, 'manage_afterEdit', None) is not None:
......@@ -338,12 +377,12 @@ class ERP5Conduit(XMLSyncUtilsMixin):
simulate=simulate,
reset=reset,
**kw)['conflict_list']
elif keyword == self.history_tag and not simulate:
elif keyword == HISTORY_TAG and not simulate:
# This is the case where we have to call addNode
conflict_list += self.addNode(xml=subnode, object=object,
force=force, simulate=simulate,
reset=reset, **kw)['conflict_list']
elif keyword in (self.local_role_tag, self.local_permission_tag) and not simulate:
elif keyword in (LOCAL_ROLE_TAG, LOCAL_PERMISSION_TAG) and not simulate:
# This is the case where we have to update Roles or update permission
#LOG('ERP5Conduit.updateNode', DEBUG, 'we will add a local role')
#user = self.getSubObjectId(xml)
......@@ -390,7 +429,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
Check if it is a simple property
not an attribute @type it's a metadata
"""
bad_list = (self.history_exp, self.attribute_type_exp,)
bad_list = (HISTORY_EXP,)
value = xml.get('select')
if value is not None:
for bad_string in bad_list:
......@@ -410,24 +449,29 @@ class ERP5Conduit(XMLSyncUtilsMixin):
"""
if xpath is None:
return context
result_list = self.extract_id_from_xpath.findall(xpath)
result_list = EXTRACT_ID_FROM_XPATH.findall(xpath)
if get_target_parent:
result_list = result_list[:-1]
first_object = True
while result_list:
object_block = result_list[0][0]
sub_context_id = result_list[0][3]
sub_context = context._getOb(sub_context_id, None)
_get_ob_accessor = getattr(context, '_getOb', None)
if _get_ob_accessor is not None:
# Some ERP5 objects doe not implement Folder
# like coordinates objects
sub_context = _get_ob_accessor(sub_context_id, None)
if first_object:
first_object = False
elif sub_context is not None:
context = sub_context
else:
# Ignore non existing objects
LOG('ERP5Conduit', INFO, 'sub document of %s not found with id:%r'%\
(context.getPath(), sub_context_id))
pass
#LOG('ERP5Conduit', INFO, 'sub document of %s not found with id:%r'%\
#(context.getPath(), sub_context_id))
xpath = xpath.replace(object_block, '', 1)
result_list = self.extract_id_from_xpath.findall(xpath)
result_list = EXTRACT_ID_FROM_XPATH.findall(xpath)
if get_target_parent:
result_list = result_list[:-1]
return context
......@@ -447,7 +491,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
security.declareProtected(Permissions.AccessContentsInformation,
'isHistoryAdd')
def isHistoryAdd(self, xml):
bad_list = (self.history_exp,)
bad_list = (HISTROY_EXP,)
value = xml.get('select')
if value is not None:
for bad_string in bad_list:
......@@ -458,88 +502,6 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return -1
return 0
security.declareProtected(Permissions.AccessContentsInformation,
'isSubObjectModification')
@deprecated
def isSubObjectModification(self, xml):
"""
Check if it is a modification from an subobject
"""
good_list = (self.sub_object_exp,)
value = xml.attrib.get('select', None)
if value is not None:
for good_string in good_list:
if good_string.search(value) is not None:
return 1
return 0
security.declareProtected(Permissions.AccessContentsInformation,
'getSubObjectDepth')
@deprecated
def getSubObjectDepth(self, xml):
"""
Give the Depth of a subobject modification
0 means, no depth
1 means it is a subobject
2 means it is more depth than subobject
"""
#LOG('getSubObjectDepth',0,'xml.tag: %s' % xml.tag)
if xml.xpath('name()') in self.XUPDATE_TAG:
i = 0
if xml.xpath('name()') in self.XUPDATE_INSERT:
i = 1
#LOG('getSubObjectDepth',0,'xml2.tag: %s' % xml.tag)
value = xml.attrib.get('select', None)
if value is not None:
#LOG('getSubObjectDepth',0,'subnode.nodeValue: %s' % subnode.nodeValue)
if self.sub_sub_object_exp.search(value) is not None:
return 2 # This is sure in all cases
elif self.sub_object_exp.search(value) is not None:
#new_select = self.getSubObjectSelect(value) # Still needed ???
#if self.getSubObjectSelect(new_select) != new_select:
# return (2 - i)
#return (1 - i)
return (2 - i)
elif self.object_exp.search(value) is not None:
return (1 - i)
return 0
security.declareProtected(Permissions.ModifyPortalContent,
'changeSubObjectSelect')
@deprecated
def changeSubObjectSelect(self, xml):
"""
Return a string wich is the selection for the subobject
ex: for "/object[@id='161']/object[@id='default_address']/street_address"
it returns "/object[@id='default_address']/street_address"
"""
select = xml.attrib.get('select')
if self.object_exp.search(select) is not None:
s = '/'
if re.search('/.*/', select) is not None: # This means we have more than just object
new_value = select[select.find(s, select.find(s)+1):]
else:
new_value = '/'
select = new_value
xml.attrib['select'] = select
security.declareProtected(Permissions.AccessContentsInformation,
'getSubObjectId')
@deprecated
def getSubObjectId(self, xml):
"""
Return the id of the subobject in an xupdate modification
"""
object_id = None
value = xml.attrib.get('select', None)
if value is not None:
if self.object_exp.search(value) is not None:
s = "'"
first = value.find(s) + 1
object_id = value[first:value.find(s, first)]
return object_id
return object_id
security.declareProtected(Permissions.AccessContentsInformation,
'getHistoryIdFromSelect')
def getHistoryIdFromSelect(self, xml):
......@@ -547,10 +509,10 @@ class ERP5Conduit(XMLSyncUtilsMixin):
Return the id of the subobject in an xupdate modification
"""
object_id = None
value = xml.attrib.get('select', None)
value = xml.get('select')
if value is not None:
if self.history_exp.search(value) is not None:
s = self.history_tag
if HISTORY_EXP.search(value) is not None:
s = HISTORY_TAG
object_id = value[value.find(s):]
object_id = object_id[object_id.find("'") + 1:]
object_id = object_id[:object_id.find("'")]
......@@ -565,51 +527,36 @@ class ERP5Conduit(XMLSyncUtilsMixin):
"""
xml = self.convertToXml(xml)
for subnode in xml:
if subnode.xpath('local-name()') == self.xml_object_tag:
if subnode.xpath('local-name()') == XML_OBJECT_TAG:
if object_id == subnode.get('id'):
return subnode
return None
security.declareProtected(Permissions.AccessContentsInformation,
'getAttribute')
@deprecated
def getAttribute(self, xml, param):
"""
Retrieve the given parameter from the xml
"""
return xml.attrib.get(param, None)
security.declareProtected(Permissions.AccessContentsInformation,
'getObjectProperty')
@deprecated
def getObjectProperty(self, property, xml, data_type=None):
"""
Retrieve the given property
"""
xml = self.convertToXml(xml)
# document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
for subnode in xml:
if subnode.xpath('local-name()') == property:
return self.convertXmlValue(subnode)
return None
security.declareProtected(Permissions.ModifyPortalContent,
'replaceIdFromXML')
def replaceIdFromXML(self, xml, attribute_name, new_id, as_string=True):
"""
return a xml with id replace by a new id
"""XXX argument old_attribute_name is missing
XXX name of method is not good, because content is not necessarily XML
return a xml with id replaced by a new id
"""
if isinstance(xml, str):
xml = etree.XML(xml, parser=parser)
else:
#copy of xml object for modification
# copy of xml object for modification
xml = deepcopy(xml)
object_element = xml.find('object')
del object_element.attrib['id']
if attribute_name == 'id':
del object_element.attrib['gid']
else:
del object_element.attrib['id']
object_element.attrib[attribute_name] = new_id
if as_string:
return etree.tostring(xml)
return etree.tostring(xml, encoding="utf-8")
return xml
def getXMLFromObjectWithId(self, object, xml_mapping):
security.declareProtected(Permissions.AccessContentsInformation,
'getXMLFromObjectWithId')
def getXMLFromObjectWithId(self, object, xml_mapping, context_document=None):
"""
return the xml with Id of Object
"""
......@@ -618,22 +565,30 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return xml
func = getattr(object, xml_mapping, None)
if func is not None:
xml = func()
try:
xml = func(context_document=context_document)
except TypeError:
# The method does not accept parameters
xml = func()
return xml
def getXMLFromObjectWithGid(self, object, gid, xml_mapping, as_string=True):
security.declareProtected(Permissions.AccessContentsInformation,
'getXMLFromObjectWithGid')
def getXMLFromObjectWithGid(self, object, gid, xml_mapping, as_string=True, context_document=None):
"""
return the xml with Gid of Object
"""
xml_with_id = self.getXMLFromObjectWithId(object, xml_mapping)
xml_with_id = self.getXMLFromObjectWithId(object, xml_mapping, context_document=context_document)
return self.replaceIdFromXML(xml_with_id, 'gid', gid, as_string=as_string)
def getXMLFromObjectWithRid(self, object, rid, xml_mapping, as_string=True):
security.declareProtected(Permissions.AccessContentsInformation,
'getXMLFromObjectWithRid')
def getXMLFromObjectWithRid(self, object, rid, xml_mapping, as_string=True, context_document=None):
"""
return the xml with Rid of Object
"""
xml_id = self.getXMLFromObjectWithId(object, xml_mapping)
xml_id = self.getXMLFromObjectWithId(object, xml_mapping, context_document=context_document)
xml_rid = self.replaceIdFromXML(xml_id, 'rid', rid, as_string=as_string)
return xml_rid
......@@ -668,16 +623,6 @@ class ERP5Conduit(XMLSyncUtilsMixin):
"""
return xml.get('type')
security.declareProtected(Permissions.AccessContentsInformation,
'getXupdateObjectType')
@deprecated
def getXupdateObjectType(self, xml):
"""
Retrieve the portal type from an xupdate
XXXX This should not be used any more !!! XXXXXXXXXXX
"""
return xml.xpath('string(.//*[name() == "xupdate:attribute"][@name = "portal_type"])') or None
security.declareProtected(Permissions.ModifyPortalContent, 'newObject')
def newObject(self, object=None, xml=None, simulate=False,
reset_local_roles=True, reset_workflow=True):
......@@ -698,12 +643,13 @@ class ERP5Conduit(XMLSyncUtilsMixin):
xml = xml[0]
for subnode in xml.xpath('*'):
#get only Element nodes (not Comments or Processing instructions)
if subnode.xpath('name()') not in self.NOT_EDITABLE_PROPERTY:
if subnode.xpath('name()') not in NOT_EDITABLE_PROPERTY_LIST:
keyword_type = self.getPropertyType(subnode)
# This is the case where the property is a list
keyword = subnode.xpath('name()')
args[keyword] = self.convertXmlValue(subnode, keyword_type)
elif subnode.xpath('local-name()') in self.ADDABLE_PROPERTY + (self.xml_object_tag,):
elif subnode.xpath('local-name()') in ADDABLE_PROPERTY_LIST\
+ (XML_OBJECT_TAG,):
self.addNode(object=object, xml=subnode, force=True)
# We should first edit the object
args = self.getFormatedArgs(args=args)
......@@ -734,15 +680,6 @@ class ERP5Conduit(XMLSyncUtilsMixin):
status[keyword] = value
return status
security.declareProtected(Permissions.AccessContentsInformation,
'getXupdateElementList')
@deprecated
def getXupdateElementList(self, xml):
"""
Retrieve the list of xupdate:element subnodes
"""
return xml.xpath('|'.join(['.//*[name() = "%s"]' % name for name in self.XUPDATE_ELEMENT]))
security.declareProtected(Permissions.AccessContentsInformation,
'getElementFromXupdate')
def getElementFromXupdate(self, xml):
......@@ -751,7 +688,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
This method simulate an xupdate transformation on given XML.
It transform the xupdate into node handleable by Conduit
"""
if xml.xpath('name()') in self.XUPDATE_ELEMENT:
if xml.xpath('name()') == XUPDATE_ELEMENT:
new_node = Element(xml.get('name'), nsmap=xml.nsmap)
for subnode in xml.findall('{%s}attribute' % xml.nsmap['xupdate']):
new_node.attrib.update({subnode.get('name'): subnode.text})
......@@ -761,7 +698,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
new_node.text = xml.text
new_node.tail = xml.tail
return new_node
if xml.xpath('name()') in (self.XUPDATE_UPDATE):
if xml.xpath('name()') == XUPDATE_UPDATE:
# This condition seems not used anymore and not efficient
# Usage of xupdate_processor is recommanded
result = u'<'
......@@ -794,9 +731,10 @@ class ERP5Conduit(XMLSyncUtilsMixin):
Return the list of workflow actions
"""
action_list = []
if xml.xpath('name()') in self.XUPDATE_ELEMENT:
if xml.xpath('name()') == XUPDATE_ELEMENT:
action_list.append(xml)
return action_list
# XXX not sure code bellow is still used ?
for subnode in xml:
if subnode.xpath('local-name()') == self.action_tag:
action_list.append(subnode)
......@@ -811,30 +749,35 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return None
if data_type is None:
data_type = self.getPropertyType(node)
if data_type == self.none_type:
if data_type == NONE_TYPE:
return None
data = node.text
if data is not None and isinstance(data, unicode):
data = data.encode('utf-8')
elif data is None and data_type in self.text_type_list:
elif data is None and data_type in TEXT_TYPE_LIST:
return ''
# We can now convert string in tuple, dict, binary...
if data_type in self.list_type_list:
if data_type in LIST_TYPE_LIST:
data = unmarshaller(node[0])
elif data_type in self.text_type_list:
elif data_type in TEXT_TYPE_LIST:
data = unescape(data)
elif data_type in self.data_type_list:
elif data_type == BOOLEAN_TYPE:
if data == 'False':
data = False
elif data == 'True':
data = True
else:
raise TypeError('Boolean type not expected:%r' % (data,))
elif data_type in DATA_TYPE_LIST:
if data is None:
# data is splitted inside block_data nodes
data = ''.join([standard_b64decode(block.text) for\
block in node.iterchildren()])
elif data_type in self.pickle_type_list:
data = pickle.loads(standard_b64decode(data))
elif data_type in self.date_type_list:
elif data_type == DATE_TYPE:
data = DateTime(data)
elif data_type in self.int_type_list:
elif data_type == INT_TYPE :
data = int(data)
elif data_type == self.boolean_type:
elif data_type == BOOLEAN_TYPE:
if data == 'False':
data = False
elif data == 'True':
......@@ -880,7 +823,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
xml_doc_string=previous_xml)
if MARSHALLER_NAMESPACE_URI in subnode.nsmap.values():
xpath_expression = original_xpath_expression
get_target_parent = subnode.xpath('name()') in self.XUPDATE_INSERT
get_target_parent = subnode.xpath('name()') in XUPDATE_INSERT_LIST
context = self.getContextFromXpath(object, xpath_expression,
get_target_parent=get_target_parent)
base_xpath_expression = xpath_expression\
......@@ -900,7 +843,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
to avoid ambiguity
"""
xpath_expression = original_xpath_expression
get_target_parent = subnode.xpath('name()') in self.XUPDATE_INSERT
get_target_parent = subnode.xpath('name()') in XUPDATE_INSERT_LIST
context = self.getContextFromXpath(object, xpath_expression,
get_target_parent=get_target_parent)
base_xpath_expression = xpath_expression\
......@@ -915,14 +858,14 @@ class ERP5Conduit(XMLSyncUtilsMixin):
dict(xml=xupdated_node,
object=context,
xpath_expression=base_xpath_expression)
elif subnode.xpath('name()') in self.XUPDATE_INSERT_OR_ADD:
elif subnode.xpath('name()') in XUPDATE_INSERT_OR_ADD_LIST:
conflict_list += self.addNode(xml=subnode, object=object,
previous_xml=previous_xml,
**kw)['conflict_list']
elif subnode.xpath('name()') in self.XUPDATE_DEL:
elif subnode.xpath('name()') == XUPDATE_DEL:
conflict_list += self.deleteNode(xml=subnode, object=object,
previous_xml=previous_xml, **kw)
elif subnode.xpath('name()') in self.XUPDATE_UPDATE:
elif subnode.xpath('name()') == XUPDATE_UPDATE:
conflict_list += self.updateNode(xml=subnode, object=object,
previous_xml=previous_xml, **kw)
......@@ -942,12 +885,17 @@ class ERP5Conduit(XMLSyncUtilsMixin):
"""
# We first test if the status in not already inside the workflow_history
wf_history = object.workflow_history
if wf_history.has_key(wf_id):
if wf_id in wf_history:
action_list = wf_history[wf_id]
else: action_list = []
else:
return True # addable
addable = True
time = status.get('time')
for action in action_list:
this_one = True
if time > action.get('time'):
# action in the past are not append
addable = False
for key in action.keys():
if status[key] != action[key]:
this_one = False
......@@ -965,8 +913,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
own Conduit.
"""
#LOG('ERP5Conduit.addNode',0,'portal_type: |%s|' % str(portal_type))
object.newContent(portal_type=portal_type, id=object_id)
subobject = object._getOb(object_id)
subobject = object.newContent(portal_type=portal_type, id=object_id)
return subobject, True, True
security.declareProtected(Permissions.ModifyPortalContent, 'addWorkflowNode')
......@@ -1006,9 +953,9 @@ class ERP5Conduit(XMLSyncUtilsMixin):
#'user:%r | roles:%r' % (user, roles))
#user = roles[0]
#roles = roles[1:]
if xml.xpath('local-name()') == self.local_role_tag:
if xml.xpath('local-name()') == LOCAL_ROLE_TAG:
object.manage_setLocalRoles(user, roles)
elif xml.xpath('local-name()') == self.local_group_tag:
elif xml.xpath('local-name()') == LOCAL_GROUP_TAG:
object.manage_setLocalGroupRoles(user, roles)
security.declareProtected(Permissions.ModifyPortalContent, 'addLocalPermissionNode')
......@@ -1026,7 +973,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
#LOG('local_role: ',0,'permission: %s roles: %s' % (repr(permission),repr(roles)))
#user = roles[0]
#roles = roles[1:]
if xml.xpath('local-name()') == self.local_permission_tag:
if xml.xpath('local-name()') == LOCAL_PERMISSION_TAG:
object.manage_setLocalPermissions(permission, roles)
return conflict_list
......@@ -1064,6 +1011,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
"""
This is the method calling to create an object
"""
# XXX We can not find an object with remote id
if object_id is None:
object_id = xml.get('id')
if object_id is not None:
......@@ -1071,7 +1019,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
sub_object = object._getOb(object_id, None)
if sub_object is None: # If so, it doesn't exist
portal_type = ''
if xml.xpath('local-name()') == self.xml_object_tag:
if xml.xpath('local-name()') == XML_OBJECT_TAG:
portal_type = self.getObjectType(xml)
sub_object, reset_local_roles, reset_workflow = self.constructContent(
object,
......@@ -1096,6 +1044,24 @@ class ERP5Conduit(XMLSyncUtilsMixin):
"""
return self.deleteObject(object, object_id)
def getContentType(self):
"""Content-Type of binded data
"""
return 'text/xml'
def generateDiff(self, old_data, new_data):
"""return xupdate node
"""
return getXupdateObject(old_data, new_data)
def applyDiff(self, original_data, diff):
"""Use xuproc for computing patched content
"""
# XXX xuproc does not support passing
# etree objetcs
diff = etree.tostring(diff)
return etree.tostring(xuproc.applyXUpdate(xml_xu_string=diff,
xml_doc_string=original_data), encoding='utf-8')
# def getGidFromXML(self, xml, namespace, gid_from_xml_list):
# """
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
......@@ -29,7 +30,6 @@
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from Products.ERP5SyncML.SyncCode import SyncCode
from zLOG import LOG
......@@ -49,13 +49,15 @@ class ERP5ConduitTitleGid(ERP5Conduit):
"""
return object.getTitle()
def getGidFromXML(self, xml, namespace, gid_from_xml_list):
def getGidFromXML(self, xml, gid_from_xml_list):
"""
return the Gid composed of FirstName and LastName generate with a peace of
xml
"""
first_name = xml.xpath('string(.//syncml:object//syncml:first_name)')
last_name = xml.xpath('string(.//syncml:object//syncml:last_name)')
first_name = xml.xpath('string(.//syncml:object//syncml:first_name)',
namespaces=xml.nsmap)
last_name = xml.xpath('string(.//syncml:object//syncml:last_name)',
namespaces=xml.nsmap)
gid = "%s %s" % (first_name, last_name)
if gid in gid_from_xml_list or gid == ' ':
return False
......
......@@ -28,7 +28,9 @@
##############################################################################
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from Products.ERP5Type import Permissions
from AccessControl import ClassSecurityInfo
from StringIO import StringIO
# Declarative security
security = ClassSecurityInfo()
......@@ -42,6 +44,8 @@ class ERP5DocumentConduit(ERP5Conduit):
# Declarative security
security = ClassSecurityInfo()
security.declareProtected(Permissions.AccessContentsInformation, 'getGidFromObject')
def getGidFromObject(self, object):
"""
return the Gid generate with the reference, object, language of the object
......@@ -49,4 +53,3 @@ class ERP5DocumentConduit(ERP5Conduit):
return "%s-%s-%s" %\
(object.getReference(), object.getVersion(), object.getLanguage())
......@@ -46,7 +46,7 @@ from zLOG import LOG
class ERP5ShopOrderConduit(ERP5Conduit):
"""
This conduit is used in the synchronisation process of Storever and ERP5 to convert
This conduit is used in the synchronization process of Storever and ERP5 to convert
a Storever Shop Order to a ERP5 Sale Order.
Don't forget to add this base categories in portal_category :
'hd_size', 'memory_size', 'optical_drive', 'keyboard_layout', 'cpu_type'
......@@ -297,7 +297,7 @@ class ERP5ShopOrderConduit(ERP5Conduit):
security.declarePrivate('updateObjProperty')
def updateObjProperty(self, object, property, kw, key):
"""
This function update the property of an object with a given value stored in a dictionnary. This function help the Conduit to make decision about the synchronisation of values.
This function update the property of an object with a given value stored in a dictionnary. This function help the Conduit to make decision about the synchronization of values.
Example of call : self.updateObjProperty(person_object, 'DefaultAddressStreetAddress', kw, 'address')
......@@ -505,7 +505,7 @@ class ERP5ShopOrderConduit(ERP5Conduit):
# # TODO : do the same things for each single information
# # TODO : before doing something working well in every case, copy the previou value in the comment field to traceback the modification and let me evaluate the solidity of my algorithm
# # TODO : perhaps it's possible to factorize the code using a generic function
# Synchronise the street address
# Synchronize the street address
# Solution (d'apres seb)
# machin = getattr (object, methos)
......@@ -706,7 +706,7 @@ class ERP5ShopOrderConduit(ERP5Conduit):
# Create a nice title (without discontinued) from the product title
product_title = self.niceTitle(kw['product_title'])
# Synchronise every data
# Synchronize every data
product_object.setDescription(kw['product_description'])
product_object.setTitle(product_title)
# # TODO : I don't know where to put this value,
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
......@@ -29,10 +30,9 @@
from Products.ERP5SyncML.Conduit.VCardConduit import VCardConduit
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from Products.ERP5SyncML.SyncCode import SyncCode
from zLOG import LOG, INFO, DEBUG, TRACE
from zLOG import LOG, INFO
class SharedVCardConduit(VCardConduit, SyncCode):
class SharedVCardConduit(VCardConduit):
"""
A conduit is in charge to read data from a particular structure,
and then to save this data in another structure.
......@@ -45,55 +45,52 @@ class SharedVCardConduit(VCardConduit, SyncCode):
# Declarative security
security = ClassSecurityInfo()
def getGidFromObject(self, object):
"""
return the Gid composed of FirstName_LastName generate with the object
"""
gid_list = []
if object.getFirstName() not in ('', None):
if object.getFirstName():
gid_list.append(object.getFirstName())
gid_list.append('_')
if object.getLastName() not in ('', None):
if object.getLastName():
gid_list.append(object.getLastName())
sql_kw = {}
sql_kw['portal_type'] = 'Person'
sql_kw['title'] = object.getTitle()
sql_kw['id'] = '<'+object.getId()
sql_kw['id'] = {'query': object.getId(), 'range': 'max'}
results = object.portal_catalog.countResults(**sql_kw)[0][0]
LOG('getGidFromObject', DEBUG, 'getId:%s, getTitle:%s' % (object.getId(), object.getTitle()))
LOG('getGidFromObject, number of results :', DEBUG, results)
#LOG('getGidFromObject', INFO, 'getId:%s, getTitle:%s' % (object.getId(), object.getTitle()))
#LOG('getGidFromObject, number of results :', INFO, results)
if int(results) > 0:
gid_list.append('__')
gid_list.append(str(int(results)+1))
gid = ''.join(gid_list)
LOG('getGidFromObject gid :', DEBUG, gid)
#LOG('getGidFromObject gid :', INFO, gid)
return gid
def getGidFromXML(self, vcard, namespace, gid_from_xml_list):
def getGidFromXML(self, vcard, gid_from_xml_list):
"""
return the Gid composed of FirstName and LastName generate with a vcard
"""
vcard_dict = self.vcard2Dict(vcard)
gid_from_vcard_list = []
if vcard_dict.has_key('first_name') and \
vcard_dict['first_name'] not in ('', None):
if vcard_dict.get('first_name'):
gid_from_vcard_list.append(vcard_dict['first_name'])
gid_from_vcard_list.append('_')
if vcard_dict.has_key('last_name') and \
vcard_dict['last_name'] not in ('', None):
if vcard_dict.get('last_name'):
gid_from_vcard_list.append(vcard_dict['last_name'])
gid_from_vcard = ''.join(gid_from_vcard_list)
LOG('getGidFromXML, gid_from_vcard :', DEBUG, gid_from_vcard)
#LOG('getGidFromXML, gid_from_vcard :', INFO, gid_from_vcard)
number = len([item for item in gid_from_xml_list if item.startswith(gid_from_vcard)])
LOG('getGidFromXML, gid_from_xml_list :', DEBUG, gid_from_xml_list)
LOG('getGidFromXML, number :', DEBUG, number)
if number > 0:
#LOG('getGidFromXML, gid_from_xml_list :', INFO, gid_from_xml_list)
#LOG('getGidFromXML, number :', INFO, number)
if number:
gid_from_vcard_list.append('__')
gid_from_vcard_list.append(str(number+1))
#it's mean for 3 persons a a a, the gid will be
#a_, a___2 a___3
gid_from_vcard = ''.join(gid_from_vcard_list)
LOG('getGidFromXML, returned gid_from_vcard :', DEBUG, gid_from_vcard)
#LOG('getGidFromXML, returned gid_from_vcard :', INFO, gid_from_vcard)
return gid_from_vcard
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
......@@ -29,31 +30,22 @@
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from Products.ERP5Type.Utils import convertToUpperCase
from Products.CMFCore.utils import getToolByName
from Products.ERP5SyncML.SyncCode import SyncCode
from Products.ERP5SyncML.Subscription import Subscription
from Acquisition import aq_base, aq_inner, aq_chain, aq_acquire
from ZODB.POSException import ConflictError
import difflib
from zLOG import LOG
class VCardConduit(ERP5Conduit, SyncCode):
class VCardConduit(ERP5Conduit):
"""
A conduit is in charge to read data from a particular structure,
and then to save this data in another structure.
VCardConduit is a peace of code to update VCards from text stream
VCardConduit is a piece of code to update VCards from text stream
"""
# Declarative security
security = ClassSecurityInfo()
security.declareProtected(Permissions.AccessContentsInformation, '__init__')
def __init__(self):
self.args = {}
security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
def addNode(self, xml=None, object=None, previous_xml=None,
......@@ -69,7 +61,8 @@ class VCardConduit(ERP5Conduit, SyncCode):
portal_type = 'Person' #the VCard can just use Person
if sub_object is None:
new_object, reset_local_roles, reset_workflow = ERP5Conduit.constructContent(self, object, object_id, portal_type)
new_object, reset_local_roles, reset_workflow =\
ERP5Conduit.constructContent(self, object, object_id, portal_type)
else: #if the object exist, it juste must be update
new_object = sub_object
#LOG('addNode', 0, 'new_object:%s, sub_object:%s' % (new_object, sub_object))
......@@ -109,7 +102,7 @@ class VCardConduit(ERP5Conduit, SyncCode):
"""
return the a list of CTType capabilities supported
"""
return self.MEDIA_TYPE.values()
return ('text/xml', 'text/vcard', 'text/x-vcard',)
def getCapabilitiesVerCTList(self, capabilities_ct_type):
"""
......@@ -117,8 +110,8 @@ class VCardConduit(ERP5Conduit, SyncCode):
"""
#add here the other version supported
verCTTypeList = {}
verCTTypeList[self.MEDIA_TYPE['TEXT_VCARD']]=('3.0',)
verCTTypeList[self.MEDIA_TYPE['TEXT_XVCARD']]=('2.1',)
verCTTypeList['text/vcard'] = ('3.0',)
verCTTypeList['text/x-vcard'] = ('2.1',)
return verCTTypeList[capabilities_ct_type]
def getPreferedCapabilitieVerCT(self):
......@@ -132,7 +125,7 @@ class VCardConduit(ERP5Conduit, SyncCode):
"""
return the prefered capabilitie VerCT
"""
prefered_type = self.MEDIA_TYPE['TEXT_XVCARD']
prefered_type = 'text/x-vcard'
return prefered_type
def changePropertyEncoding(self, property_parameters_list,
......@@ -143,7 +136,7 @@ class VCardConduit(ERP5Conduit, SyncCode):
encoding=''
for item in property_parameters_list :
if item.has_key('ENCODING'):
if ENCODING in item:
encoding = item['ENCODING']
property_value_list_well_incoded=[]
......@@ -218,3 +211,28 @@ class VCardConduit(ERP5Conduit, SyncCode):
#LOG('edit_dict =',0,edit_dict)
return edit_dict
security.declareProtected(Permissions.ModifyPortalContent,
'replaceIdFromXML')
def replaceIdFromXML(self, xml, attribute_name, new_id, as_string=True):
"""
Return the Same vlue
"""
return xml
def getContentType(self):
"""Content-Type of binded data
"""
return 'text/vcard'
def generateDiff(self, old_data, new_data):
"""return unified diff for plain-text documents
"""
diff = '\n'.join(difflib.unified_diff(old_data.splitlines(),
new_data.splitlines()))
return diff
def applyDiff(self, original_data, diff):
"""Use difflib to patch original_data
"""
raise NotImplementedError('patch unified diff')
......@@ -27,25 +27,13 @@
#
##############################################################################
from Products.ERP5Type.Globals import PersistentMapping
from time import gmtime,strftime # for anchors
from SyncCode import SyncCode
from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName
from Acquisition import Implicit, aq_base
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Type.Base import Base
from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO
import md5
from base64 import b64encode, b64decode, b16encode, b16decode
#class Conflict(SyncCode, Implicit):
class Conflict(SyncCode, Base):
class SyncMLConflict(Base):
"""
object_path : the path of the obect
keyword : an identifier of the conflict
......@@ -53,92 +41,28 @@ class Conflict(SyncCode, Base):
subscriber_value : the value sent by the remote box
"""
isIndexable = ConstantGetter('isIndexable', value=False)
# Make sure RAD generated accessors at the class level
def __init__(self, object_path=None, keyword=None, xupdate=None,
publisher_value=None, subscriber_value=None, subscriber=None):
self.object_path=object_path
self.keyword = keyword
self.setLocalValue(publisher_value)
self.setRemoteValue(subscriber_value)
self.subscriber = subscriber
self.resetXupdate()
self.copy_path = None
def getObjectPath(self):
"""
get the object path
"""
return self.object_path
def getPublisherValue(self):
"""
get the domain
"""
return self.publisher_value
def getXupdateList(self):
"""
get the xupdate wich gave an error
"""
xupdate_list = []
if len(self.xupdate)>0:
for xupdate in self.xupdate:
xupdate_list+= [xupdate]
return xupdate_list
def resetXupdate(self):
"""
Reset the xupdate list
"""
self.xupdate = PersistentMapping()
meta_type = 'ERP5 Conflict'
portal_type = 'SyncML Conflict'
def setXupdate(self, xupdate):
"""
set the xupdate
"""
if xupdate == None:
self.resetXupdate()
else:
self.xupdate = self.getXupdateList() + [xupdate]
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
def setXupdateList(self, xupdate):
"""
set the xupdate
"""
self.xupdate = xupdate
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.SyncMLConflict )
def setLocalValue(self, value):
"""
get the domain
"""
try:
self.publisher_value = value
except TypeError: # It happens when we try to store StringIO
self.publisher_value = None
def getSubscriberValue(self):
"""
get the domain
"""
return self.subscriber_value
def setRemoteValue(self, value):
"""
get the domain
"""
try:
self.subscriber_value = value
except TypeError: # It happens when we try to store StringIO
self.subscriber_value = None
def _getPortalSynchronizationTool(self):
return getToolByName(self.getPortalObject(), 'portal_synchronizations')
def applyPublisherValue(self):
"""
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
p_sync.applyPublisherValue(self)
def applyPublisherDocument(self):
......@@ -146,7 +70,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
p_sync.applyPublisherDocument(self)
def getPublisherDocument(self):
......@@ -154,7 +78,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
return p_sync.getPublisherDocument(self)
def getPublisherDocumentPath(self):
......@@ -162,7 +86,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
return p_sync.getPublisherDocumentPath(self)
def getSubscriberDocument(self):
......@@ -170,7 +94,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
return p_sync.getSubscriberDocument(self)
def getSubscriberDocumentPath(self):
......@@ -178,7 +102,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
return p_sync.getSubscriberDocumentPath(self)
def applySubscriberDocument(self):
......@@ -186,49 +110,18 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
p_sync.applySubscriberDocument(self)
def applySubscriberValue(self, object=None):
"""
get the domain
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
p_sync.applySubscriberValue(self, object=object)
def setSubscriber(self, subscriber):
"""
set the domain
"""
self.subscriber = subscriber
def getSubscriber(self):
"""
get the domain
"""
return self.subscriber
def getKeyword(self):
Return the grand parent subscriber
"""
get the domain
"""
return self.keyword
def getPropertyId(self):
"""
get the property id
"""
return self.keyword
def getCopyPath(self):
"""
Get the path of the copy, or None if none has been made
"""
copy_path = self.copy_path
return copy_path
def setCopyPath(self, path):
"""
"""
self.copy_path = path
return self.getParentValue().getParentValue()
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Products.ERP5SyncML.Document.SyncMLSubscription import SyncMLSubscription
from Products.ERP5Type import Permissions
from AccessControl import ClassSecurityInfo
from Products.ERP5SyncML.SyncMLConstant import ACTIVITY_PRIORITY
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
class SyncMLPublication(SyncMLSubscription):
"""Reply to request from SyncML clients,
Serve data to be synchronized.
"""
meta_type = 'ERP5 Publication'
portal_type = 'SyncML Publication' # may be useful in the future...
# Declarative security
security = ClassSecurityInfo()
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriber')
def getSubscriber(self, subscription_url):
"""
return the subscriber corresponding the to subscription_url
"""
subscriber = None
for subscription in self.contentValues(portal_type='SyncML Subscription'):
if subscription.getSubscriptionUrlString() == subscription_url:
subscriber = subscription
break
return subscriber
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriberList')
def getSubscriberList(self):
"""
Get the list of subscribers
"""
return self.contentValues(portal_type='SyncML Subscription')
security.declareProtected(Permissions.ModifyPortalContent,
'resetSubscriberList')
def resetSubscriberList(self):
"""
Reset all subscribers
"""
id_list = []
for subscriber in self.contentValues(portal_type='SyncML Subscription'):
subscriber.resetSignatureList()
id_list.append(subscriber.getId())
self.activate(activity='SQLQueue',
tag=self.getId(),
after_tag=id_list,
priority=ACTIVITY_PRIORITY).manage_delObjects(id_list)
security.declareProtected(Permissions.AccessContentsInformation,
'getConflictList')
def getConflictList(self, *args, **kw):
"""
Return the list of conflicts from all subscribers
"""
conflict_list = []
for subscriber in self.getSubscriberList():
conflict_list.extend(subscriber.getConflictList())
return conflict_list
security.declarePrivate('createUnrestrictedSubscriber')
@UnrestrictedMethod
def createUnrestrictedSubscriber(self, **kw):
"""Create a subscriber even if user is anonymous
"""
kw['portal_type'] = 'SyncML Subscription'
return self.newContent(**kw)
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
# Danièle Vanbaelinghem <daniele@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet
from zLOG import LOG, DEBUG, INFO
from Products.ERP5SyncML.Utils import PdataHelper
import md5
_MARKER = []
class SyncMLSignature(XMLObject):
"""
status -- SENT, CONFLICT...
md5_object -- An MD5 value of a given document
#uid -- The UID of the document
id -- the ID of the document
gid -- the global id of the document
rid -- the uid of the document on the remote database,
only needed on the server.
xml -- the xml of the object at the time where it was synchronized
"""
meta_type = 'ERP5 Signature'
portal_type = 'SyncML Signature'
isIndexable = ConstantGetter('isIndexable', value=False)
# Make sure RAD generated accessors at the class level
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.DublinCore
, PropertySheet.Reference
, PropertySheet.Data
, PropertySheet.Document
, PropertySheet.SyncMLSignature )
security.declareProtected(Permissions.ModifyPortalContent, 'setData')
def setData(self, value):
"""
set the XML corresponding to the object
"""
if value:
# convert the string to Pdata
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
#data, size = pdata_wrapper()
self._setData(pdata_wrapper)
self.setTemporaryData(None) # We make sure that the data will not be erased
self.setContentMd5(pdata_wrapper.getContentMd5())
else:
self._setData(None)
self.setContentMd5(None)
security.declareProtected(Permissions.AccessContentsInformation, 'getData')
def getData(self, default=_MARKER):
"""
get the XML corresponding to the object
"""
if self.hasData():
return str(self._baseGetData())
if default is _MARKER:
return self._baseGetData()
else:
return self._baseGetData(default)
security.declareProtected(Permissions.ModifyPortalContent,
'setTemporaryData')
def setTemporaryData(self, value):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
"""
if value:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setTemporaryData(pdata_wrapper)
else:
self._setTemporaryData(None)
security.declareProtected(Permissions.AccessContentsInformation,
'getTemporaryData')
def getTemporaryData(self, default=_MARKER):
"""
get the temp xml
"""
if self.hasTemporaryData():
return str(self._baseGetTemporaryData())
if default is _MARKER:
return self._baseGetTemporaryData()
else:
return self._baseGetTemporaryData(default)
security.declareProtected(Permissions.AccessContentsInformation, 'checkMD5')
def checkMD5(self, xml_string):
"""
check if the given md5_object returns the same things as
the one stored in this signature, this is very usefull
if we want to know if an objects has changed or not
Returns 1 if MD5 are equals, else it returns 0
"""
return ((md5.new(xml_string).hexdigest()) == self.getContentMd5())
security.declareProtected(Permissions.ModifyPortalContent, 'setPartialData')
def setPartialData(self, value):
"""
Set the partial string we will have to
deliver in the future
"""
if value is not None:
if not isinstance(value, PdataHelper):
value = PdataHelper(self.getPortalObject(), value)
self._setPartialData(value)
self.setLastDataPartialData(value.getLastPdata())
else:
self._setPartialData(None)
self.setLastDataPartialData(None)
security.declareProtected(Permissions.ModifyPortalContent, 'setLastData')
def setLastData(self, value):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
"""
if value is not None:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setLastData(pdata_wrapper)
else:
self._setLastData(None)
security.declareProtected(Permissions.AccessContentsInformation,
'getPartialData')
def getPartialData(self, default=_MARKER):
"""
get the patial xml
"""
if self.hasPartialData():
return str(self._baseGetPartialData())
if default is _MARKER:
return self._baseGetPartialData()
else:
return self._baseGetPartialData(default)
security.declareProtected(Permissions.ModifyPortalContent, 'appendPartialData')
def appendPartialData(self, value):
"""
Append the partial string we will have to deliver in the future
"""
if value is not None:
if not isinstance(value, PdataHelper):
value = PdataHelper(self.getPortalObject(), value)
last_data = value.getLastPdata()
if self.hasLastDataPartialData():
last_data_partial_data = self.getLastDataPartialData()
last_data_partial_data.next = value._data
self.setLastDataPartialData(last_data_partial_data)
else:
self.setPartialData(value)
self.setLastDataPartialData(last_data)
#security.declareProtected(Permissions.AccessContentsInformation,
#'getFirstChunkPdata')
#def getFirstChunkPdata(self, size_lines):
#"""
#"""
#chunk = [self.getPartialData().data]
#size = chunk[0].count('\n')
#current = self.getPartialData()
#next = current.next
#while size < size_lines and next is not None:
#current = next
#size += current.data.count('\n')
#chunk.append(current.data)
#next = current.next
#if size == size_lines:
#self.setPartialData(next)
#elif size > size_lines:
#overflow = size - size_lines
#data_list = chunk[-1].split('\n')
#chunk[-1] = '\n'.join(data_list[:-overflow])
#current.data = '\n'.join(data_list[-overflow:])
#self.setPartialData(current)
#return ''.join(chunk)
def getFirstPdataChunk(self, max_len):
"""
"""
#chunk, rest_in_queue = self._baseGetPartialData().\
#getFirstPdataChunkAndRestInQueue(max_len)
partial_data = self._baseGetPartialData()
chunk = partial_data[:max_len]
rest_in_queue = partial_data[max_len:]
if rest_in_queue is not None:
self.setPartialData(rest_in_queue)
return str(chunk)
security.declareProtected(Permissions.ModifyPortalContent,
'setSubscriberXupdate')
def setSubscriberXupdate(self, value):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
"""
if value is not None:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setSubscriberXupdate(pdata_wrapper)
else:
self._setSubscriberXupdate(None)
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriberXupdate')
def getSubscriberXupdate(self, default=_MARKER):
"""
get the patial xml
"""
if self.hasSubscriberXupdate():
return str(self._baseGetSubscriberXupdate())
if default is _MARKER:
return self._baseGetSubscriberXupdate()
else:
return self._baseGetSubscriberXupdate(default)
security.declareProtected(Permissions.ModifyPortalContent,
'setPublisherXupdate')
def setPublisherXupdate(self, value):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
"""
if value is not None:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setPublisherXupdate(pdata_wrapper)
else:
self._setPublisherXupdate(None)
security.declareProtected(Permissions.AccessContentsInformation,
'getPublisherXupdate')
def getPublisherXupdate(self, default=_MARKER):
"""
get the patial xml
"""
if self.hasPublisherXupdate():
return str(self._baseGetPublisherXupdate())
if default is _MARKER:
return self._baseGetPublisherXupdate()
else:
return self._baseGetPublisherXupdate(default)
security.declareProtected(Permissions.ModifyPortalContent,
'reset')
def reset(self):
"""Clear Signature and change validation_state to not_synchronized
"""
if self.getValidationState() != 'not_synchronized':
self.drift()
self.setPartialData(None)
self.setTemporaryData(None)
def getConflictList(self):
"""
Return the actual action for a partial synchronization
"""
return self.contentValues()
# returned_conflict_list = []
# if getattr(self, 'conflict_list', None) is None:
# return returned_conflict_list
# if len(self.conflict_list)>0:
# returned_conflict_list.extend(self.conflict_list)
# return returned_conflict_list
def setConflictList(self, conflict_list):
"""
Return the actual action for a partial synchronization
"""
return
# if conflict_list is None or conflict_list == []:
# self.resetConflictList()
# else:
# self.conflict_list = conflict_list
def resetConflictList(self):
"""
Return the actual action for a partial synchronization
"""
return
#self.conflict_list = PersistentMapping()
def delConflict(self, conflict):
"""
Delete provided conflict object
"""
self.manage_delObjects([conflict.getId(),])
# conflict_list = []
# for c in self.getConflictList():
# #LOG('delConflict, c==conflict',0,c==aq_base(conflict))
# if c != aq_base(conflict):
# conflict_list += [c]
# if conflict_list != []:
# self.setConflictList(conflict_list)
# else:
# self.resetConflictList()
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from AccessControl import ClassSecurityInfo
from Acquisition import aq_base
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.Utils import deprecated
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO, WARNING
from base64 import b16encode, b16decode
from Products.ERP5SyncML.XMLSyncUtils import getConduitByName
from Products.ERP5SyncML.SyncMLConstant import MAX_OBJECTS, ACTIVITY_PRIORITY
from warnings import warn
_MARKER = []
class SyncMLSubscription(XMLObject):
"""
Subscription hold the definition of a master ODB
from/to which a selection of objects will be synchronized
Subscription defined by::
publication_url -- a URI to a publication
subscription_url -- URL of ourselves
destination_path -- the place where objects are stored
query -- a query which defines a local set of documents which
are going to be synchronized
xml_mapping -- a PageTemplate to map documents to XML
gpg_key -- the name of a gpg key to use
Subscription also holds private data to manage
the synchronization. We choose to keep an MD5 value for
all documents which belong to the synchronization process::
signatures -- a dictionnary which contains the signature
of documents at the time they were synchronized
session_id -- it defines the id of the session
with the server.
last_anchor - it defines the id of the last synchronization
next_anchor - it defines the id of the current synchronization
Subscription inherit of File because the Signature use method _read_data
which have the need of a __r_jar not None.
During the initialization of a Signature this __p_jar is None
"""
meta_type = 'ERP5 Subscription'
portal_type = 'SyncML Subscription' # may be useful in the future...
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.DublinCore
, PropertySheet.Reference
, PropertySheet.Login
, PropertySheet.Url
, PropertySheet.Gpg
, PropertySheet.Data
, PropertySheet.SyncMLSubscription
, PropertySheet.SyncMLSubscriptionConstraint )
security.declareProtected(Permissions.AccessContentsInformation,
'isOneWayFromServer')
def isOneWayFromServer(self):
return self.getPortalType() == 'SyncML Subscription' and \
self.getSyncmlAlertCode() == 'one_way_from_server'
security.declareProtected(Permissions.AccessContentsInformation,
'isOneWayFromClient')
def isOneWayFromClient(self):
return self.getParentValue().getPortalType() == 'SyncML Publication' and \
self.getSyncmlAlertCode() == 'one_way_from_client'
security.declareProtected(Permissions.AccessContentsInformation,
'getSynchronizationType')
def getSynchronizationType(self, default=_MARKER):
"""Deprecated alias of getSyncmlAlertCode
"""
warn('Use getSyncmlAlertCode instead', DeprecationWarning)
if default is _MARKER:
code = self.getSyncmlAlertCode()
else:
code = self.getSyncmlAlertCode(default=default)
return code
security.declarePrivate('checkCorrectRemoteSessionId')
def checkCorrectRemoteSessionId(self, session_id):
"""
We will see if the last session id was the same
wich means that the same message was sent again
return True if the session id was not seen, False if already seen
"""
if self.getLastSessionId() == session_id:
return False
self.setLastSessionId(session_id)
return True
security.declarePrivate('checkCorrectRemoteMessageId')
def checkCorrectRemoteMessageId(self, message_id):
"""
We will see if the last message id was the same
wich means that the same message was sent again
return True if the message id was not seen, False if already seen
"""
if self.getLastMessageId() == message_id:
return False
self.setLastMessageId(message_id)
return True
security.declareProtected(Permissions.ModifyPortalContent,
'initLastMessageId')
def initLastMessageId(self, last_message_id=0):
"""
set the last message id to 0
"""
self.setLastMessageId(last_message_id)
security.declareProtected(Permissions.AccessContentsInformation,
'getXmlBindingGeneratorMethodId')
def getXmlBindingGeneratorMethodId(self, default=_MARKER, force=False):
"""
return the xml mapping
"""
if self.isOneWayFromServer() and not force:
return None
if default is _MARKER:
return self._baseGetXmlBindingGeneratorMethodId()
else:
return self._baseGetXmlBindingGeneratorMethodId(default=default)
security.declareProtected(Permissions.AccessContentsInformation,
'getGidFromObject')
def getGidFromObject(self, object, encoded=True):
"""
Returns the object gid
"""
o_base = aq_base(object)
gid = None
# first try with new method
gid_generator = self.getGidGeneratorMethodId("")
if gid_generator not in ("", None) and getattr(self, gid_generator, None):
raw_gid = getattr(self, gid_generator)(object)
else:
# old way using the conduit
conduit_name = self.getConduitModuleId()
conduit = getConduitByName(conduit_name)
raw_gid = conduit.getGidFromObject(object)
if isinstance(raw_gid, unicode):
raw_gid = raw_gid.encode('ascii', 'ignore')
if encoded:
gid = b16encode(raw_gid)
else:
gid = raw_gid
return gid
security.declareProtected(Permissions.AccessContentsInformation,
'getObjectFromGid')
def getObjectFromGid(self, gid, use_list_method=True):
"""
This tries to get the object with the given gid
This uses the query if it exist and use_list_method is True
"""
if len(gid)%2 != 0:
#something encode in base 16 is always a even number of number
#if not, b16decode will failed
return None
signature = self.getSignatureFromGid(gid)
# First look if we do already have the mapping between
# the id and the gid
destination = self.getSourceValue()
if signature is not None and signature.getReference():
document_path = signature.getReference()
document = self.getPortalObject().unrestrictedTraverse(document_path, None)
if document is not None:
return document
#LOG('entering in the slow loop of getObjectFromGid !!!', WARNING,
#self.getPath())
if use_list_method:
object_list = self.getObjectList(gid=b16decode(gid))
for document in object_list:
document_gid = self.getGidFromObject(document)
if document_gid == gid:
return document
#LOG('getObjectFromGid', DEBUG, 'returning None')
return None
security.declareProtected(Permissions.AccessContentsInformation,
'getObjectFromId')
def getObjectFromId(self, id):
"""
return the object corresponding to the id
"""
object_list = self.getObjectList(id=id)
o = None
for object in object_list:
if object.getId() == id:
o = object
break
return o
security.declareProtected(Permissions.AccessContentsInformation,
'getObjectList')
def getObjectList(self, **kw):
"""
This returns the list of sub-object corresponding
to the query
"""
folder = self.getSourceValue()
list_method_id = self.getListMethodId()
if list_method_id is not None and isinstance(list_method_id, str):
result_list = []
query_method = folder.unrestrictedTraverse(list_method_id, None)
if query_method is not None:
result_list = query_method(context_document=self, **kw)
else:
raise KeyError, 'This Subscriber %s provide no list method:%r'\
% (self.getPath(), list_method_id)
else:
raise KeyError, 'This Subscriber %s provide no list method with id:%r'\
% (self.getPath(), list_method_id)
# XXX Access all objects is very costly
return [x for x in result_list
if not getattr(x, '_conflict_resolution', False)]
security.declarePrivate('generateNewIdWithGenerator')
def generateNewIdWithGenerator(self, object=None, gid=None):
"""
This tries to generate a new Id
"""
id_generator = self.getSynchronizationIdGeneratorMethodId()
if id_generator is not None:
o_base = aq_base(object)
new_id = None
if callable(id_generator):
new_id = id_generator(object, gid=gid)
elif getattr(o_base, id_generator, None) is not None:
generator = getattr(object, id_generator)
new_id = generator()
else:
# This is probably a python script
generator = getattr(object, id_generator)
new_id = generator(object=object, gid=gid)
#LOG('generateNewIdWithGenerator, new_id: ', DEBUG, new_id)
return new_id
return None
security.declareProtected(Permissions.ModifyPortalContent,
'incrementSessionId')
def incrementSessionId(self):
"""
increment and return the session id
"""
session_id = self.getSessionId()
session_id += 1
self._setSessionId(session_id)
self.resetMessageId() # for a new session, the message Id must be reset
return session_id
security.declareProtected(Permissions.ModifyPortalContent,
'incrementMessageId')
def incrementMessageId(self):
"""
return the message id
"""
message_id = self.getMessageId(0)
message_id += 1
self._setMessageId(message_id)
return message_id
security.declareProtected(Permissions.ModifyPortalContent,
'resetMessageId')
def resetMessageId(self):
"""
set the message id to 0
"""
self._setMessageId(0)
security.declareProtected(Permissions.ModifyPortalContent,
'createNewAnchor')
def createNewAnchor(self):
"""
set a new anchor
"""
self.setLastAnchor(self.getNextAnchor())
self.setNextAnchor(DateTime())
security.declareProtected(Permissions.ModifyPortalContent,
'resetAnchorList')
def resetAnchorList(self):
"""
reset both last and next anchors
"""
self.setLastAnchor(None)
self.setNextAnchor(None)
security.declareProtected(Permissions.AccessContentsInformation,
'getSignatureFromObjectId')
def getSignatureFromObjectId(self, id):
"""
return the signature corresponding to the id
### Use a reverse dictionary will be usefull
to handle changes of GIDs
"""
document = None
# XXX very slow
for signature in self.objectValues():
document = signature.getSourceValue()
if document is not None:
if id == document.getId():
document = signature
break
return document
security.declareProtected(Permissions.AccessContentsInformation,
'getSignatureFromGid')
def getSignatureFromGid(self, gid):
"""
return the signature corresponding to the gid
"""
return self._getOb(gid, None)
security.declareProtected(Permissions.AccessContentsInformation,
'getGidList')
def getGidList(self):
"""
Returns the list of gids from signature
"""
return [id for id in self.getObjectIds()]
security.declareProtected(Permissions.AccessContentsInformation,
'getSignatureList')
@deprecated
def getSignatureList(self):
"""
Returns the list of Signatures
"""
return self.contentValues(portal_type='SyncML Signature')
security.declareProtected(Permissions.AccessContentsInformation,
'hasSignature')
def hasSignature(self, gid):
"""
Check if there's a signature with this uid
"""
return self.getSignatureFromGid(gid) is not None
security.declareProtected(Permissions.ModifyPortalContent,
'resetSignatureList')
def resetSignatureList(self):
"""
Reset all signatures in activities
"""
object_id_list = [id for id in self.getObjectIds()]
object_list_len = len(object_id_list)
for i in xrange(0, object_list_len, MAX_OBJECTS):
current_id_list = object_id_list[i:i+MAX_OBJECTS]
self.activate(activity='SQLQueue',
tag=self.getId(),
priority=ACTIVITY_PRIORITY).manage_delObjects(current_id_list)
security.declareProtected(Permissions.AccessContentsInformation,
'getConflictList')
def getConflictList(self, *args, **kw):
"""
Return the list of all conflicts from all signatures
"""
conflict_list = []
for signature in self.objectValues():
conflict_list.extend(signature.getConflictList())
return conflict_list
security.declareProtected(Permissions.ModifyPortalContent,
'removeRemainingObjectPath')
def removeRemainingObjectPath(self, object_path):
"""
We should now wich objects should still
synchronize
"""
remaining_object_list = self.getProperty('remaining_object_path_list')
if remaining_object_list is None:
# it is important to let remaining_object_path_list to None
# it means it has not beeing initialised yet
return
new_list = []
new_list.extend(remaining_object_list)
while object_path in new_list:
new_list.remove(object_path)
self._edit(remaining_object_path_list=new_list)
security.declareProtected(Permissions.ModifyPortalContent,
'initialiseSynchronization')
def initialiseSynchronization(self):
"""
Set the status of every object as not_synchronized
XXX Improve method to not fetch many objects in unique transaction
"""
LOG('Subscription.initialiseSynchronization()', 0, self.getPath())
for signature in self.contentValues(portal_type='SyncML Signature'):
# Change the status only if we are not in a conflict mode
if signature.getValidationState() not in ('conflict',
'conflict_resolved_with_merge',
'conflict_resolved_with_client_command_winning'):
if self.getIsActivityEnabled():
signature.activate(tag=self.getId(), activity='SQLQueue',
priority=ACTIVITY_PRIORITY).reset()
else:
signature.reset()
self._edit(remaining_object_path_list=None)
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Products.ERP5Type.Globals import Persistent, PersistentMapping
from SyncCode import SyncCode
from Subscription import Subscription
from Products.ERP5Type import Permissions
from Products.ERP5Type.Core.Folder import Folder
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import PropertySheet
from zLOG import LOG
def addSubscriber( self, id, title='', REQUEST=None ):
"""
Add a new Category and generate UID by calling the
ZSQLCatalog
"""
o = Subscriber( id ,'')
self._setObject( id, o )
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1)
return o
class Subscriber(Subscription):
"""
This is used to store a subscriber, with :
subscribtion_url
signatures -- a dictionnary which contains the signature
of documents at the time they were synchronized.
last_anchor - it defines the id of the last synchronisation
next_anchor - it defines the id of the current synchronisation
"""
def __init__(self, id, subscription_url):
"""
constructor
"""
self.subscription_url = subscription_url
self.last_anchor = '00000000T000000Z'
self.next_anchor = '00000000T000000Z'
self.session_id = 0
Folder.__init__(self, id)
def ReceiveDocuments(self):
"""
We receive documents from a subsriber
we add if document does not exist
we update if the local signature did not change
we keep as conflict to be solved by user if
local signature changed (between 2 syncs)
"""
def ConfirmReception(self):
"""
?????
Send ACK for a group of documents
"""
def SendDocuments(self):
"""
We send all the updated documents (ie. documents not marked
as conflicting)
"""
def addPublication( self, id, title='', REQUEST=None ):
"""
Add a new Category and generate UID by calling the
ZSQLCatalog
"""
o = Publication( id, '', '', '', '', '')
self._setObject( id, o )
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1)
return o
class Publication(Subscription):
"""
Publication defined by
publication_url
destination_path - the place where objects are and will be stored
query
xml_mapping
Contains:
list_subscribers -- a list of subsbribtions
"""
meta_type='ERP5 Publication'
portal_type='SyncML Publication' # may be useful in the future...
icon = None
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.SimpleItem )
allowed_types = ( 'Signatures',)
# Declarative security
security = ClassSecurityInfo()
security.declareProtected(Permissions.ManagePortal,
'manage_editProperties',
'manage_changeProperties',
'manage_propertiesForm',
)
# Declarative constructors
constructors = (addPublication,)
# Constructor
def __init__(self, id, title, publication_url, destination_path,
source_uri, query, xml_mapping, conduit, gpg_key, id_generator,
media_type, authentication_format,
authentication_type, activity_enabled, synchronize_with_erp5_sites,
sync_content_type):
"""
constructor
"""
self.id = id
self.setActivityEnabled(activity_enabled)
self.publication_url = publication_url
self.destination_path = destination_path
self.setSourceURI(source_uri)
self.setQuery(query)
self.xml_mapping = xml_mapping
self.domain_type = self.PUB
self.gpg_key = gpg_key
self.setMediaType(media_type)
self.setSynchronizationIdGenerator(id_generator)
self.setConduit(conduit)
Folder.__init__(self, id)
self.title = title
self.setAuthenticationFormat(authentication_format)
self.setAuthenticationType(authentication_type)
self.setSyncContentType(sync_content_type)
self.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites)
def getPublicationUrl(self):
"""
return the publication url
"""
return self.publication_url
def getLocalUrl(self):
"""
return the publication url
"""
return self.publication_url
def setPublicationUrl(self, publication_url):
"""
return the publication url
"""
self.publication_url = publication_url
def addSubscriber(self, subscriber):
"""
Add a new subscriber to the publication
"""
# We have to remove the subscriber if it already exist (there were probably a reset on the client)
self.delSubscriber(subscriber.getSubscriptionUrl())
new_id = subscriber.getId()
if new_id is None:
new_id = str(self.generateNewId())
subscriber.id = new_id
self._setObject(new_id, subscriber)
def getSubscriber(self, subscription_url):
"""
return the subscriber corresponding the to subscription_url
"""
o = None
for sub in self.getSubscriberList():
if sub.getSubscriptionUrl() == subscription_url:
o = sub
break
return o
def getSubscriberList(self):
"""
Get the list of subscribers
"""
return self.objectValues()
def delSubscriber(self, subscription_url):
"""
Delete a subscriber for this publication
"""
for o in self.getSubscriberList():
if o.getSubscriptionUrl() == subscription_url:
self.manage_delObjects(o.id)
break
def resetAllSubscribers(self):
"""
Reset all subscribers
"""
id_list = []
for subscriber in self.getSubscriberList():
subscriber.resetAllSignatures()
id_list.append(subscriber.getId())
self.activate(activity='SQLQueue',
tag=self.getId(),
after_tag=id_list,
priority=self.PRIORITY).manage_delObjects(id_list)
def getConflictList(self):
"""
Return the list of conflicts from all subscribers
"""
conflict_list = []
for subscriber in self.getSubscriberList():
conflict_list.extend(subscriber.getConflictList())
return conflict_list
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2003 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import smtplib # to send emails
from Publication import Publication,Subscriber
from Signature import Signature
from XMLSyncUtils import XMLSyncUtils
from Conduit.ERP5Conduit import ERP5Conduit
from Products.CMFCore.utils import getToolByName
from Products.ERP5Security.ERP5UserManager import ERP5UserManager
from Products.PluggableAuthService.interfaces.plugins import\
IAuthenticationPlugin
from AccessControl.SecurityManagement import newSecurityManager
import commands
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO, WARNING
from lxml import etree
from lxml.builder import ElementMaker
from SyncCode import SYNCML_NAMESPACE
nsmap = {'syncml' : SYNCML_NAMESPACE}
E = ElementMaker(namespace=SYNCML_NAMESPACE, nsmap=nsmap)
class PublicationSynchronization(XMLSyncUtils):
"""
Receive the first XML message from the client
"""
def PubSyncInit(self, publication=None, xml_client=None, subscriber=None,
sync_type=None):
"""
Read the client xml message
Send the first XML message from the server
"""
LOG('PubSyncInit', INFO, 'Starting... publication: %s' % (publication.getPath()))
#the session id is set at the same value of those of the client
subscriber.setSessionId(self.getSessionIdFromXml(xml_client))
#same for the message id
subscriber.setMessageId(self.getMessageIdFromXml(xml_client))
#at the begining of the synchronization the subscriber is not authenticated
subscriber.setAuthenticated(False)
#the last_message_id is 1 because the message that
#we are about to send is the message 1
subscriber.initLastMessageId(1)
alert = None
# Get informations from the body
if xml_client is not None: # We have received a message
last_anchor = self.getAlertLastAnchor(xml_client)
next_anchor = self.getAlertNextAnchor(xml_client)
alert = self.checkAlert(xml_client)
alert_code = self.getAlertCodeFromXML(xml_client)
cred = self.checkCred(xml_client)
#the source and the target of the subscriber are reversed compared
# to those of the publication :
subscriber.setSourceURI(self.getTargetURI(xml_client))
subscriber.setTargetURI(self.getSourceURI(xml_client))
cmd_id = 1 # specifies a SyncML message-unique command identifier
#create element 'SyncML' with a default namespace
xml = E.SyncML()
# syncml header
xml.append(self.SyncMLHeader(subscriber.getSessionId(),
subscriber.getMessageId(),
subscriber.getSubscriptionUrl(),
publication.getPublicationUrl()))
# syncml body
sync_body = E.SyncBody()
xml.append(sync_body)
#at the begining, the code is initialised at UNAUTHORIZED
auth_code = self.UNAUTHORIZED
if not cred:
auth_code = self.AUTH_REQUIRED
# LOG("PubSyncInit there's no credential !!!", INFO,'')
# Prepare the xml message for the Sync initialization package
sync_body.append(self.SyncMLChal(cmd_id, "SyncHdr",
publication.getPublicationUrl(), subscriber.getSubscriptionUrl(),
publication.getAuthenticationFormat(),
publication.getAuthenticationType(), auth_code))
cmd_id += 1
# chal message
xml_status, cmd_id = self.SyncMLStatus(
xml_client,
auth_code,
cmd_id,
next_anchor,
subscription=subscriber)
sync_body.extend(xml_status)
else:
# If slow sync, then resend everything
if alert_code == self.SLOW_SYNC and \
subscriber.getNextAnchor() != self.NULL_ANCHOR:
LOG('Warning !!!, reseting client synchronization for subscriber:', WARNING,
subscriber.getPath())
subscriber.resetAllSignatures()
subscriber.resetAnchors()
# Check if the last time synchronization is the same as the client one
if subscriber.getNextAnchor() != last_anchor:
if not last_anchor:
LOG('PubSyncInit', INFO, 'anchor null')
else:
mess = '\nsubscriber.getNextAnchor:\t%s\nsubscriber.getLastAnchor:\t%s\
\nlast_anchor:\t\t\t%s\nnext_anchor:\t\t\t%s' % \
(subscriber.getNextAnchor(),
subscriber.getLastAnchor(),
last_anchor,
next_anchor)
LOG('PubSyncInit Anchors', INFO, mess)
else:
subscriber.setNextAnchor(next_anchor)
(authentication_format, authentication_type, data) = \
self.getCred(xml_client)
if authentication_type == publication.getAuthenticationType():
authentication_format = publication.getAuthenticationFormat()
decoded = subscriber.decode(authentication_format, data)
if decoded and ':' in decoded:
(login, password) = decoded.split(':')
uf = self.getPortalObject().acl_users
for plugin_name, plugin in uf._getOb('plugins').listPlugins(
IAuthenticationPlugin ):
if plugin.authenticateCredentials(
{'login':login, 'password':password}) is not None:
subscriber.setAuthenticated(True)
LOG("PubSyncInit Authentication Accepted", INFO, '')
auth_code = self.AUTH_ACCEPTED
#here we must log in with the user authenticated :
user = uf.getUserById(login).__of__(uf)
newSecurityManager(None, user)
subscriber.setUser(login)
break
else:
auth_code = self.UNAUTHORIZED
#in all others cases, the auth_code is set to UNAUTHORIZED
if auth_code == self.UNAUTHORIZED:
LOG("PubSyncInit Authentication Failed !! with login :", INFO, login)
# Prepare the xml message for the Sync initialization package
if auth_code == self.AUTH_ACCEPTED:
xml_status, cmd_id = self.SyncMLStatus(xml_client, auth_code,
cmd_id, next_anchor,
subscription=subscriber)
sync_body.extend(xml_status)
# alert message
sync_body.append(self.SyncMLAlert(cmd_id, sync_type,
subscriber.getTargetURI(),
subscriber.getSourceURI(),
subscriber.getLastAnchor(),
next_anchor))
cmd_id += 1
else:
# chal message
sync_body.append(self.SyncMLChal(cmd_id, "SyncHdr",
publication.getPublicationUrl(),
subscriber.getSubscriptionUrl(),
publication.getAuthenticationFormat(),
publication.getAuthenticationType(),
auth_code))
cmd_id += 1
xml_status, cmd_id = self.SyncMLStatus(xml_client,
self.AUTH_REQUIRED, cmd_id,
next_anchor,
subscription=subscriber)
sync_body.extend(xml_status)
# We have to set every object as NOT_SYNCHRONIZED
subscriber.startSynchronization()
else:
# We have started the sync from the server (may be for a conflict
# resolution)
raise ValueError, "the syncml message is None. Maybe a synchronisation \
has been started from the server (forbiden)"
# a synchronisation is always starded from a client and can't be from
# a server !
sync_body.append(E.Final())
xml_string = etree.tostring(xml, encoding='utf-8', pretty_print=True)
if publication.getSyncContentType() == self.CONTENT_TYPE['SYNCML_WBXML']:
xml_string = self.xml2wbxml(xml_string)
self.sendResponse(from_url=publication.getPublicationUrl(),
to_url=subscriber.getSubscriptionUrl(),
sync_id=publication.getTitle(),
xml=xml_string, domain=publication,
content_type=publication.getSyncContentType())
return {'has_response':1, 'xml':xml_string}
def PubSyncModif(self, publication, xml_client):
"""
The modidification message for the publication
"""
return self.SyncModif(publication, xml_client)
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
# Danièle Vanbaelinghem <daniele@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Products.ERP5Type.Globals import PersistentMapping
from time import gmtime,strftime # for anchors
from SyncCode import SyncCode
from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName
from Acquisition import Implicit, aq_base
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Type.Base import Base
from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet
from Products.ERP5.Document import Document
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO
import cStringIO
from OFS.Image import Pdata
from OFS.Image import File
import md5
from base64 import b64encode, b64decode, b16encode, b16decode
class Signature(Folder, SyncCode, File):
"""
status -- SENT, CONFLICT...
md5_object -- An MD5 value of a given document
#uid -- The UID of the document
id -- the ID of the document
gid -- the global id of the document
rid -- the uid of the document on the remote database,
only needed on the server.
xml -- the xml of the object at the time where it was synchronized
"""
isIndexable = ConstantGetter('isIndexable', value=False)
# Make sure RAD generated accessors at the class level
# Constructor
def __init__(self,
id=None,
rid=None,
status=None,
xml_string=None,
object=None):
Folder.__init__(self, id)
File.__init__(self, id, '', '')
if object is not None:
self.setPath(object.getPhysicalPath())
self.setObjectId(object.getId())
else:
self.setPath(None)
self.setId(id)
self.setRid(rid)
self.status = status
self.setXML(xml_string)
self.setPartialXML(None)
self.action = None
self.setTempXML(None)
self.resetConflictList()
self.md5_string = None
self.force = 0
self.setSubscriberXupdate(None)
self.setPublisherXupdate(None)
self.last_data_partial_xml = None
def setStatus(self, status):
"""
set the Status (see SyncCode for numbers)
"""
self.status = status
if status == self.SYNCHRONIZED:
temp_xml = self.getTempXML()
self.setForce(0)
if temp_xml is not None:
# This happens when we have sent the xml
# and we just get the confirmation
self.setXML(temp_xml)
self.setTempXML(None)
self.setPartialXML(None)
self.setSubscriberXupdate(None)
self.setPublisherXupdate(None)
if len(self.getConflictList())>0:
self.resetConflictList()
# XXX This may be a problem, if the document is changed
# during a synchronization
self.setLastSynchronizationDate(DateTime())
self.getParentValue().removeRemainingObjectPath(self.getPath())
if status == self.NOT_SYNCHRONIZED:
self.setTempXML(None)
self.setPartialXML(None)
elif status in (self.PUB_CONFLICT_MERGE, self.SENT):
# We have a solution for the conflict, don't need to keep the list
self.resetConflictList()
def getStatus(self):
"""
get the Status (see SyncCode for numbers)
"""
return self.status
def getPath(self):
"""
get the force value (if we need to force update or not)
"""
return getattr(self, 'path', None)
def setPath(self, path):
"""
set the force value (if we need to force update or not)
"""
self.path = path
def getForce(self):
"""
get the force value (if we need to force update or not)
"""
return self.force
def setForce(self, force):
"""
set the force value (if we need to force update or not)
"""
self.force = force
def getLastModificationDate(self):
"""
get the last modfication date, so that we don't always
check the xml
"""
return getattr(self, 'modification_date', None)
def setLastModificationDate(self,value):
"""
set the last modfication date, so that we don't always
check the xml
"""
setattr(self, 'modification_date', value)
def getLastSynchronizationDate(self):
"""
get the last modfication date, so that we don't always
check the xml
"""
return getattr(self, 'synchronization_date', None)
def setLastSynchronizationDate(self,value):
"""
set the last modfication date, so that we don't always
check the xml
"""
setattr(self, 'synchronization_date', value)
def hasXML(self):
"""
return True if the xml is available
"""
return bool(getattr(self, 'xml', None))
def setXML(self, xml):
"""
set the XML corresponding to the object
"""
if xml is not None:
# convert the string to Pdata if the big size
file = cStringIO.StringIO(xml)
self.xml, size = self.getParentValue()._read_data(file)
self.setTempXML(None) # We make sure that the xml will not be erased
self.setMD5(xml)
else:
self.xml = None
def getXML(self, default=None):
"""
get the XML corresponding to the object
"""
#Never return empty string
if self.hasXML():
if isinstance(self.xml, Pdata):
return str(self.xml)
elif isinstance(self.xml, str):
return self.xml
else:
raise ValueError, "the self.xml haven't good type"
else:
return default
def hasTempXML(self):
"""
Return true if the temp_xml is available
"""
return bool(getattr(self, 'temp_xml', None))
def setTempXML(self, xml):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
"""
if xml is not None:
file = cStringIO.StringIO(xml)
self.temp_xml, size = self.getParentValue()._read_data(file)
else:
self.temp_xml = None
def getTempXML(self, default=None):
"""
get the temp xml
"""
if self.hasTempXML():
if isinstance(self.temp_xml, Pdata):
return str(self.temp_xml)
elif isinstance(self.temp_xml, str):
return self.temp_xml
else:
raise ValueError, "the self.xml haven't good type"
else:
return default
def setSubscriberXupdate(self, xupdate):
"""
set the full temp xupdate
"""
self.subscriber_xupdate = xupdate
def getSubscriberXupdate(self):
"""
get the full temp xupdate
"""
return self.subscriber_xupdate
def setPublisherXupdate(self, xupdate):
"""
set the full temp xupdate
"""
self.publisher_xupdate = xupdate
def getPublisherXupdate(self):
"""
get the full temp xupdate
"""
return self.publisher_xupdate
def setMD5(self, xml):
"""
set the MD5 object of this signature
"""
self.md5_string = md5.new(xml).digest()
def getMD5(self):
"""
get the MD5 object of this signature
"""
return self.md5_string
def checkMD5(self, xml_string):
"""
check if the given md5_object returns the same things as
the one stored in this signature, this is very usefull
if we want to know if an objects has changed or not
Returns 1 if MD5 are equals, else it returns 0
"""
return ((md5.new(xml_string).digest()) == self.getMD5())
def setRid(self, rid):
"""
set the rid
"""
if isinstance(rid, unicode):
rid = rid.encode('utf-8')
self.rid = rid
def getRid(self):
"""
get the rid
"""
return getattr(self, 'rid', None)
def setId(self, id):
"""
set the id
"""
if isinstance(id, unicode):
id = id.encode('utf-8')
self.id = id
def getId(self):
"""
get the id
"""
return self.id
def getGid(self):
"""
get the gid
"""
return self.getId()
def setObjectId(self, id):
"""
set the id of the object associated to this signature
"""
if isinstance(id, unicode):
id = id.encode('utf-8')
self.object_id = id
def getObjectId(self):
"""
get the id of the object associated to this signature
"""
return getattr(self, 'object_id', None)
def hasPartialXML(self):
"""
Return true is the partial xml is available
"""
return bool(getattr(self, 'partial_xml', None))
def setPartialXML(self, xml):
"""
Set the partial string we will have to
deliver in the future
"""
if xml is not None:
# change encoding of xml to convert in file
try:
xml = xml.encode('utf-8')
except UnicodeDecodeError:
xml = xml.decode('utf-8').encode('ascii','xmlcharrefreplace')
# convert the string to Pdata if the big size
file = cStringIO.StringIO(xml)
self.partial_xml, size = self.getParentValue()._read_data(file)
if not isinstance(self.partial_xml, Pdata):
self.partial_xml = Pdata(self.partial_xml)
self.last_data_partial_xml = self.partial_xml.getLastPdata()
else:
self.partial_xml = None
self.last_data_partial_xml = None
def appendPartialXML(self, xml):
"""
Append the partial string we will have to deliver in the future
"""
if xml is not None:
try:
xml = xml.encode('utf-8')
except UnicodeDecodeError:
xml = xml.decode('utf-8').encode('ascii','xmlcharrefreplace')
file = cStringIO.StringIO(xml)
xml_append, size = self.getParentValue()._read_data(file)
if not isinstance(xml_append, Pdata):
xml_append = Pdata(xml_append)
last_data = xml_append.getLastPdata()
if self.last_data_partial_xml is not None:
self.last_data_partial_xml.next = xml_append
else:
self.partial_xml = xml_append
self.last_data_partial_xml = last_data
def getFirstChunkPdata(self, size_lines):
"""
"""
chunk = list()
chunk.append(self.partial_xml.data)
size = chunk[0].count('\n')
current = self.partial_xml
next = current.next
while size < size_lines and next is not None:
current = next
size += current.data.count('\n')
chunk.append(current.data)
next = current.next
if size == size_lines:
self.partial_xml = next
elif size > size_lines:
overflow = size - size_lines
data_list = chunk[-1].split('\n')
chunk[-1] = '\n'.join(data_list[:-overflow])
current.data = '\n'.join(data_list[-overflow:])
self.partial_xml = current
return ''.join(chunk)
def getPartialXML(self, default=None):
"""
Set the partial string we will have to
deliver in the future
"""
if self.hasPartialXML():
if isinstance(self.partial_xml, Pdata):
return str(self.partial_xml)
else:
raise ValueError, "the self.xml haven't good type"
else:
return default
def getAction(self):
"""
Return the actual action for a partial synchronization
"""
return self.action
def setAction(self, action):
"""
Return the actual action for a partial synchronization
"""
self.action = action
def getConflictList(self):
"""
Return the actual action for a partial synchronization
"""
returned_conflict_list = []
if len(self.conflict_list)>0:
returned_conflict_list.extend(self.conflict_list)
return returned_conflict_list
def resetConflictList(self):
"""
Return the actual action for a partial synchronization
"""
self.conflict_list = PersistentMapping()
def setConflictList(self, conflict_list):
"""
Return the actual action for a partial synchronization
"""
if conflict_list is None or conflict_list == []:
self.resetConflictList()
else:
self.conflict_list = conflict_list
def delConflict(self, conflict):
"""
Return the actual action for a partial synchronization
"""
conflict_list = []
for c in self.getConflictList():
#LOG('delConflict, c==conflict',0,c==aq_base(conflict))
if c != aq_base(conflict):
conflict_list += [c]
if conflict_list != []:
self.setConflictList(conflict_list)
else:
self.resetConflictList()
def getObject(self):
"""
Returns the object corresponding to this signature
"""
return self.getParentValue().getObjectFromGid(self.getObjectId())
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Products.ERP5Type.Globals import PersistentMapping
from time import gmtime, strftime # for anchors
from SyncCode import SyncCode
from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName
from Acquisition import Implicit, aq_base
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Type.Base import Base
from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet
from OFS.Image import File
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO
import md5
from base64 import b64encode, b64decode, b16encode, b16decode
def addSubscription( self, id, title='', REQUEST=None ):
"""
Add a new Subscription
"""
o = Subscription(id, '', '', '', '', '', '')
self._setObject(id, o)
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1)
return o
#class Subscription(SyncCode, Implicit):
#class Subscription(Folder, SyncCode, Implicit, Folder, Impli):
from XMLSyncUtils import XMLSyncUtils
class Subscription(Folder, XMLSyncUtils, File):
"""
Subscription hold the definition of a master ODB
from/to which a selection of objects will be synchronised
Subscription defined by::
publication_url -- a URI to a publication
subscription_url -- URL of ourselves
destination_path -- the place where objects are stored
query -- a query which defines a local set of documents which
are going to be synchronised
xml_mapping -- a PageTemplate to map documents to XML
gpg_key -- the name of a gpg key to use
Subscription also holds private data to manage
the synchronisation. We choose to keep an MD5 value for
all documents which belong to the synchronisation process::
signatures -- a dictionnary which contains the signature
of documents at the time they were synchronized
session_id -- it defines the id of the session
with the server.
last_anchor - it defines the id of the last synchronisation
next_anchor - it defines the id of the current synchronisation
Subscription inherit of File because the Signature use method _read_data
which have the need of a __r_jar not None.
During the initialization of a Signature this __p_jar is None
"""
meta_type = 'ERP5 Subscription'
portal_type = 'SyncML Subscription' # may be useful in the future...
icon = None
isIndexable = ConstantGetter('isIndexable', value=False)
user = None
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.SimpleItem )
allowed_types = ( 'Signatures',)
# Declarative constructors
constructors = (addSubscription,)
# Declarative security
security = ClassSecurityInfo()
security.declareProtected(Permissions.ManagePortal,
'manage_editProperties',
'manage_changeProperties',
'manage_propertiesForm',
)
# Constructor
def __init__(self, id, title, publication_url, subscription_url,
destination_path, source_uri, target_uri, query, xml_mapping,
conduit, gpg_key, id_generator, media_type, login,
password, activity_enabled, alert_code, synchronize_with_erp5_sites,
sync_content_type):
"""
We need to create a dictionnary of
signatures of documents which belong to the synchronisation
process
"""
self.id = id
self.setAlertCode(alert_code)
self.setActivityEnabled(activity_enabled)
self.publication_url = (publication_url)
self.subscription_url = str(subscription_url)
self.destination_path = str(destination_path)
self.setSourceURI(source_uri)
self.setTargetURI(target_uri)
self.setQuery(query)
self.setXMLMapping(xml_mapping)
self.anchor = None
self.session_id = 0
#self.signatures = PersistentMapping()
self.last_anchor = '00000000T000000Z'
self.next_anchor = '00000000T000000Z'
self.setMediaType(media_type)
self.login = login
self.password = password
self.domain_type = self.SUB
self.gpg_key = gpg_key
self.setSynchronizationIdGenerator(id_generator)
self.setConduit(conduit)
Folder.__init__(self, id)
self.title = title
self.setSyncContentType(sync_content_type)
self.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites)
def getAlertCodeList(self):
return self.CODE_LIST
def getAlertCode(self):
return getattr(self, 'alert_code', 200)
def setAlertCode(self, value):
self.alert_code = int(value)
def isOneWayFromServer(self):
return self.getDomainType() == self.SUB and \
self.getAlertCode() == self.ONE_WAY_FROM_SERVER
def getActivityEnabled(self):
"""
return true if we are using activity, false otherwise
"""
return getattr(self, 'activity_enabled', None)
def setActivityEnabled(self, activity_enabled):
"""
set if we are using activity or not
"""
self.activity_enabled = activity_enabled
def getTitle(self):
"""
getter for title
"""
return getattr(self, 'title', None)
def setTitle(self, value):
"""
setter for title
"""
self.title = value
def setSourceURI(self, value):
"""
setter for source_uri
"""
self.source_uri = value
def getSourceURI(self):
"""
getter for the source_uri (the local path of the subscription data base)
"""
return getattr(self, 'source_uri', None)
def setTargetURI(self, value):
"""
setter for target_uri
"""
self.target_uri = value
def getTargetURI(self):
"""
getter for the target_uri (the distant Publication data base we want to
synchronize with)
"""
return getattr(self, 'target_uri', None)
def setSyncContentType(self, sync_content_type):
"""
content type used by the subscriber
"""
self.sync_content_type = sync_content_type
# the varible name is sync_content_type instead of content_type because
# content_type seems to be a function name already used
def getSyncContentType(self):
"""
getter of the subscriber sync_content_type
"""
return getattr(self, 'sync_content_type', 'application/vnd.syncml+xml')
def getSynchronizationType(self, default=None):
"""
"""
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# XXX for debugging only, to be removed
#dict_sign = {}
#for o in self.getSignatureList():
#dict_sign[o.getId()] = o.getStatus()
# LOG('getSignature', DEBUG, 'signatures_status: %s' % str(dict_sign))
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
code = self.SLOW_SYNC
if len(self.getSignatureList()[:1]) > 0:
code = self.getAlertCode()
if default is not None:
code = default
#LOG('Subscription', DEBUG, 'getSynchronizationType: %s' % code)
return code
def setXMLMapping(self, value):
"""
this the name of the method used in order to set the xml
"""
if value == '':
value = None
self.xml_mapping = value
def setSynchronizeWithERP5Sites(self, synchronize_with_erp5_sites):
"""
if the synchronisation is made with another ERP5 site,
synchronize_with_erp5_sites is True, False in other case
XXX in the future, the method used to sendHttpResponse will be the same
in all cases, so this method will be useless
"""
self.synchronize_with_erp5_sites = synchronize_with_erp5_sites
def getSynchronizeWithERP5Sites(self):
"""
return True if the synchronisation is between two erp5 sites
"""
return getattr(self, 'synchronize_with_erp5_sites', True)
def checkCorrectRemoteSessionId(self, session_id):
"""
We will see if the last session id was the same
wich means that the same message was sent again
return True if the session id was not seen, False if already seen
"""
last_session_id = getattr(self, 'last_session_id', None)
if last_session_id == session_id:
return False
self.last_session_id = session_id
return True
def checkCorrectRemoteMessageId(self, message_id):
"""
We will see if the last message id was the same
wich means that the same message was sent again
return True if the message id was not seen, False if already seen
"""
last_message_id = getattr(self, 'last_message_id', None)
if last_message_id == message_id:
return False
self.last_message_id = message_id
return True
def initLastMessageId(self, last_message_id=0):
"""
set the last message id to 0
"""
self.last_message_id = last_message_id
def getLastSentMessage(self):
"""
This is the getter for the last message we have sent
"""
return getattr(self, 'last_sent_message', '')
def setLastSentMessage(self, xml):
"""
This is the setter for the last message we have sent
"""
self.last_sent_message = xml
def getDomainType(self):
"""
return the ID
"""
return self.domain_type
def getId(self):
"""
return the ID
"""
return self.id
def setId(self, id):
"""
set the ID
"""
self.id = id
def setConduit(self, value):
"""
set the Conduit
"""
self.conduit = value
def getConduit(self):
"""
get the Conduit
"""
return getattr(self, 'conduit', None)
def getQuery(self):
"""
return the query
"""
return self.query
def getGPGKey(self):
"""
return the gnupg key name
"""
return getattr(self, 'gpg_key', '')
def setGPGKey(self, value):
"""
setter for the gnupg key name
"""
self.gpg_key = value
def setQuery(self, query):
"""
set the query
"""
if query == '':
query = None
self.query = query
def getPublicationUrl(self):
"""
return the publication url
"""
return self.publication_url
def getLocalUrl(self):
"""
return the publication url
"""
return self.publication_url
def setPublicationUrl(self, publication_url):
"""
set the publication url
"""
self.publication_url = publication_url
def getXMLMapping(self, force=0):
"""
return the xml mapping
"""
if self.isOneWayFromServer() and force == 0:
return None
xml_mapping = getattr(self, 'xml_mapping', None)
return xml_mapping
def getMediaType(self):
"""
This method return the type of media used in this session,
for example, it could be "text/vcard" or "xml/text",...
"""
return getattr(self, 'media_type', self.MEDIA_TYPE['TEXT_XML'])
def setMediaType(self, media_type):
"""
set the type of media used
"""
if media_type in (None, ''):
media_type = self.MEDIA_TYPE['TEXT_XML']
self.media_type = media_type
def getLogin(self):
"""
This method return the login of this subscription
"""
return getattr(self, 'login', '')
def setLogin(self, new_login):
"""
set the login at new_login
"""
self.login = new_login
def getPassword(self):
"""
This method return the password of this subscription
"""
return getattr(self, 'password', '')
def setPassword(self, new_password):
"""
set the password at new_password
"""
self.password = new_password
def getZopeUser(self):
"""
This method return the zope user who begin the synchronization session
"""
return getattr(self, 'zope_user_name', None)
def setZopeUser(self, user_name):
"""
This method set the zope user_name
"""
self.zope_user_name = user_name
def getAuthenticationFormat(self):
"""
return the format of authentication
"""
return getattr(self, 'authentication_format', 'b64')
def getAuthenticationType(self):
"""
return the type of authentication
"""
return getattr(self, 'authentication_type', 'syncml:auth-basic')
def setAuthenticationFormat(self, authentication_format):
"""
set the format of authentication
"""
if authentication_format in (None, ''):
self.authentication_format = 'b64'
else:
self.authentication_format = authentication_format
def setAuthenticationType(self, authentication_type):
"""
set the type of authentication
"""
if authentication_type in (None, ''):
self.authentication_type = 'syncml:auth-basic'
else:
self.authentication_type = authentication_type
def getGidFromObject(self, object):
"""
Returns the object gid
"""
o_base = aq_base(object)
o_gid = None
conduit_name = self.getConduit()
conduit = self.getConduitByName(conduit_name)
gid_gen = getattr(conduit, 'getGidFromObject', None)
if callable(gid_gen):
o_gid = gid_gen(object)
else:
raise ValueError, "The conduit "+conduit_name+"seems to not have a \
getGidFromObject method and it must"
o_gid = b16encode(o_gid)
#LOG('getGidFromObject returning', DEBUG, o_gid)
return o_gid
def getObjectFromGid(self, gid):
"""
This tries to get the object with the given gid
This uses the query if it exist
"""
if len(gid)%2 != 0:
#something encode in base 16 is always a even number of number
#if not, b16decode will failed
return None
signature = self.getSignatureFromGid(gid)
# First look if we do already have the mapping between
# the id and the gid
destination = self.getDestination()
if signature is not None and signature.getPath() is not None:
o = None
try:
o = destination.getPortalObject().restrictedTraverse(signature.getPath())
except (AttributeError, KeyError, TypeError):
pass
o_id = signature.getObjectId()
#try with id param too, because gid is not catalogged
object_list = self.getObjectList(gid=b16decode(gid), id=o_id)
if o is not None and o.getId() in [document.getId() for document in object_list]:
return o
#LOG('entering in the slow loop of getObjectFromGid !!!',0,'')
object_list = self.getObjectList(gid=b16decode(gid))
for o in object_list:
o_gid = self.getGidFromObject(o)
if o_gid == gid:
return o
#LOG('getObjectFromGid', DEBUG, 'returning None')
return None
def getObjectFromId(self, id):
"""
return the object corresponding to the id
"""
object_list = self.getObjectList(id=id)
o = None
for object in object_list:
if object.getId() == id:
o = object
break
return o
def getObjectList(self, **kw):
"""
This returns the list of sub-object corresponding
to the query
"""
destination = self.getDestinationPath()
query = self.getQuery()
if query is not None and isinstance(query, str):
count = query.count("/")
if count > 0:
# There is a query path different than destination path change
# destination path for this query
cut_query = query.split('/')
if destination.endswith('/'):
destination = "%s%s" % (destination, "/".join(cut_query[:count]))
else:
destination = "%s/s" % (destination, "/".join(cut_query[:count]))
query = cut_query[count]
query_list = []
destination = self.unrestrictedTraverse(destination)
query_method = getattr(destination, query, None)
if query_method is not None:
query_list = query_method(**kw)
else:
raise KeyError, 'This Subscriber %s provide no Query: %s'\
% (self.getTitle(), query)
elif callable(query): # used in the test
destination = self.unrestrictedTraverse(destination)
query_list = query(destination)
else:
raise KeyError, 'This Subscriber %s provide no Query with id: %s'\
% (self.getTitle(), query)
return [x for x in query_list
if not getattr(x, '_conflict_resolution', False)]
def generateNewIdWithGenerator(self, object=None, gid=None):
"""
This tries to generate a new Id
"""
id_generator = self.getSynchronizationIdGenerator()
if id_generator is not None:
o_base = aq_base(object)
new_id = None
if callable(id_generator):
new_id = id_generator(object, gid=gid)
elif getattr(o_base, id_generator, None) is not None:
generator = getattr(object, id_generator)
new_id = generator()
else:
# This is probably a python script
generator = getattr(object, id_generator)
new_id = generator(object=object, gid=gid)
#LOG('generateNewIdWithGenerator, new_id: ', DEBUG, new_id)
return new_id
return None
def setSynchronizationIdGenerator(self, method):
"""
This set the method name wich allows to generate
a new id
"""
if method in ('', 'None'):
method = None
self.synchronization_id_generator = method
def getSynchronizationIdGenerator(self):
"""
This get the method name wich allows to generate a new id
"""
return getattr(self, 'synchronization_id_generator', None)
def getSubscriptionUrl(self):
"""
return the subscription url
"""
return self.subscription_url
def setSubscriptionUrl(self, subscription_url):
"""
set the subscription url
"""
self.subscription_url = subscription_url
def getDestinationPath(self):
"""
return the destination path
"""
return self.destination_path
def getDestination(self):
"""
return the destination object itself
"""
return self.unrestrictedTraverse(self.getDestinationPath())
def setDestinationPath(self, destination_path):
"""
set the destination path
"""
self.destination_path = destination_path
def getSubscription(self):
"""
return the current subscription
"""
return self
def setSessionId(self, session_id):
"""
set the session id
"""
self.session_id = session_id
def getSessionId(self):
"""
return the session id
"""
#self.session_id += 1 #to be commented
return self.session_id
def incrementSessionId(self):
"""
increment and return the session id
"""
self.session_id += 1
self.resetMessageId() # for a new session, the message Id must be reset
return self.session_id
def incrementMessageId(self):
"""
return the message id
"""
value = getattr(self, 'message_id', 0)
self.message_id = value +1
return self.message_id
def getMessageId(self):
"""
increment and return the message id
"""
return self.message_id
def resetMessageId(self):
"""
set the message id to 0
"""
self.message_id = 0
def setMessageId(self, message_id):
"""
set the message id to message_id
"""
self.message_id = message_id
def getLastAnchor(self):
"""
return the id of the last synchronisation
"""
return self.last_anchor
def getNextAnchor(self):
"""
return the id of the current synchronisation
"""
return self.next_anchor
def setLastAnchor(self, last_anchor):
"""
set the value last anchor
"""
self.last_anchor = last_anchor
def setNextAnchor(self, next_anchor):
"""
set the value next anchor
"""
# We store the old next anchor as the new last one
self.last_anchor = self.next_anchor
self.next_anchor = next_anchor
def NewAnchor(self):
"""
set a new anchor
"""
self.last_anchor = self.next_anchor
self.next_anchor = strftime("%Y%m%dT%H%M%SZ", gmtime())
def resetAnchors(self):
"""
reset both last and next anchors
"""
self.last_anchor = self.NULL_ANCHOR
self.next_anchor = self.NULL_ANCHOR
def addSignature(self, signature):
"""
add a Signature to the subscription
"""
if self.getSignatureFromGid(signature.getGid()) is not None:
self.delSignature(signature.getGid())
self._setObject(signature.getGid(), aq_base(signature))
def delSignature(self, gid):
"""
del a Signature of the subscription
"""
self._delObject(gid)
def getSignatureFromObjectId(self, id):
"""
return the signature corresponding to the id
"""
o = None
# XXX very slow
for signature in self.getSignatureList():
if id == signature.getObjectId():
o = signature
break
return o
def getSignatureFromGid(self, gid):
"""
return the signature corresponding to the gid
"""
return getattr(self, gid, None)
def getSignatureFromRid(self, rid):
"""
return the signature corresponding to the rid
"""
o = None
# XXX very slow
for signature in self.getSignatureList():
if rid == signature.getRid():
o = signature
break
return o
def getGidList(self):
"""
Returns the list of gids from signature
"""
return [id for id in self.getObjectIds()]
def getSignatureList(self):
"""
Returns the list of Signatures
"""
return self.objectValues()
def hasSignature(self, gid):
"""
Check if there's a signature with this uid
"""
return self.getSignatureFromGid(gid) is not None
def resetAllSignatures(self):
"""
Reset all signatures in activities
"""
object_id_list = [id for id in self.getObjectIds()]
object_list_len = len(object_id_list)
for i in xrange(0, object_list_len, self.MAX_OBJECTS):
current_id_list = object_id_list[i:i+self.MAX_OBJECTS]
self.activate(activity='SQLQueue',
tag=self.getId(),
priority=self.PRIORITY).manage_delObjects(current_id_list)
def getConflictList(self):
"""
Return the list of all conflicts from all signatures
"""
conflict_list = []
for signature in self.getSignatureList():
conflict_list.extend(signature.getConflictList())
return conflict_list
def getRemainingObjectPathList(self):
"""
We should now wich objects should still
synchronize
"""
return getattr(self, 'remaining_object_path_list', None)
def setRemainingObjectPathList(self, value):
"""
We should now wich objects should still
synchronize
"""
setattr(self, 'remaining_object_path_list', value)
def removeRemainingObjectPath(self, object_path):
"""
We should now wich objects should still
synchronize
"""
remaining_object_list = self.getRemainingObjectPathList()
if remaining_object_list is not None:
new_list = []
new_list.extend(remaining_object_list)
while object_path in new_list:
new_list.remove(object_path)
self.setRemainingObjectPathList(new_list)
def startSynchronization(self):
"""
Set the status of every object as NOT_SYNCHRONIZED
"""
for s in self.getSignatureList():
# Change the status only if we are not in a conflict mode
if s.getStatus() not in (self.CONFLICT,
self.PUB_CONFLICT_MERGE,
self.PUB_CONFLICT_CLIENT_WIN):
s.setStatus(self.NOT_SYNCHRONIZED)
s.setPartialXML(None)
s.setTempXML(None)
self.setRemainingObjectPathList(None)
def isAuthenticated(self):
"""
return True if the subscriber is authenticated for this session, False
in other case
"""
return getattr(self, 'is_authenticated', None)
def setAuthenticated(self, value):
"""
set at True or False the value of is_authenticated is the subscriber
is authenticated for this session or not
"""
self.is_authenticated = value
def encode(self, format, string_to_encode):
"""
return the string_to_encode encoded with format format
"""
if format in ('', None):
return string_to_encode
if format == 'b64':
return b64encode(string_to_encode)
#elif format is .... put here the other formats
else:#if there is no format corresponding with format, raise an error
LOG('encode : unknown or not implemented format : ', INFO, format)
raise ValueError, "Sorry, the server ask for the format %s but \
it's unknow or not implemented" % format
def decode(self, format, string_to_decode):
"""
return the string_to_decode decoded with format format
"""
string_to_decode = string_to_decode.encode('utf-8')
if format in ('', None):
return string_to_decode
if format == 'b64':
return b64decode(string_to_decode)
#elif format is .... put here the other formats
else:#if there is no format corresponding with format, raise an error
LOG('decode : unknown or not implemented format :', INFO, format)
raise ValueError, "Sorry, the format %s is unknow or \
not implemented" % format
def isDecodeEncodeTheSame(self, string_encoded, string_decoded, format):
"""
return True if the string_encoded is equal to string_decoded encoded
in format
"""
return self.encode(format, string_decoded) == string_encoded
def setUser(self, user):
"""
save the user logged in to log him on each transaction
"""
self.user = user
def getUser(self):
"""
retrun the user logged in
"""
return getattr(self, 'user', None)
def getConduitByName(self, conduit_name):
"""
Get Conduit Object by given name.
The Conduit can be located in Any Products according to naming Convention
Products.<Product Name>.Conduit.<Conduit Module> ,if conduit_name equal module's name.
By default Conduit must be defined in Products.ERP5SyncML.Conduit.<Conduit Module>
"""
from Products.ERP5SyncML import Conduit
if conduit_name.startswith('Products'):
path = conduit_name
conduit_name = conduit_name.split('.')[-1]
conduit_module = __import__(path, globals(), locals(), [''])
conduit = getattr(conduit_module, conduit_name)()
else:
conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]),
globals(), locals(), [''])
conduit = getattr(conduit_module, conduit_name)()
return conduit
#XXX backwards compatibility
from Products.ERP5SyncML import Signature, Conflict
Signature = Signature.Signature
Conflict = Conflict.Conflict
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2003 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import smtplib # to send emails
from Subscription import Subscription
from Signature import Signature
from XMLSyncUtils import XMLSyncUtils
import commands
from Conduit.ERP5Conduit import ERP5Conduit
from AccessControl import getSecurityManager
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO
from lxml import etree
from lxml.builder import ElementMaker
from SyncCode import SYNCML_NAMESPACE
nsmap = {'syncml' : SYNCML_NAMESPACE}
E = ElementMaker(namespace=SYNCML_NAMESPACE, nsmap=nsmap)
class SubscriptionSynchronization(XMLSyncUtils):
def SubSyncInit(self, subscription):
"""
Send the first XML message from the client
"""
#LOG('SubSyncInit',0,'starting....')
cmd_id = 1 # specifies a SyncML message-unique command identifier
subscription.NewAnchor()
subscription.initLastMessageId()
#save the actual user to use it in all the session:
user = getSecurityManager().getUser()
subscription.setZopeUser(user)
subscription.setAuthenticated(True)
#create element 'SyncML'
xml = E.SyncML()
# syncml header
xml.append(self.SyncMLHeader(subscription.incrementSessionId(),
subscription.incrementMessageId(), subscription.getPublicationUrl(),
subscription.getSubscriptionUrl(), source_name=subscription.getLogin()))
# syncml body
sync_body = E.SyncBody()
xml.append(sync_body)
# We have to set every object as NOT_SYNCHRONIZED
subscription.startSynchronization()
# alert message
sync_body.append(self.SyncMLAlert(cmd_id, subscription.getSynchronizationType(),
subscription.getTargetURI(),
subscription.getSourceURI(),
subscription.getLastAnchor(),
subscription.getNextAnchor()))
cmd_id += 1
syncml_put = self.SyncMLPut(cmd_id, subscription)
if syncml_put is not None:
sync_body.append(syncml_put)
cmd_id += 1
xml_string = etree.tostring(xml, encoding='utf-8', xml_declaration=True,
pretty_print=True)
self.sendResponse(from_url=subscription.subscription_url,
to_url=subscription.publication_url,
sync_id=subscription.getTitle(),
xml=xml_string, domain=subscription,
content_type=subscription.getSyncContentType())
return {'has_response':1, 'xml':xml_string}
def SubSyncCred (self, subscription, msg=None, RESPONSE=None):
"""
This method send crendentials
"""
cmd_id = 1 # specifies a SyncML message-unique command identifier
#create element 'SyncML' with a default namespace
xml = E.SyncML()
# syncml header
data = "%s:%s" % (subscription.getLogin(), subscription.getPassword())
data = subscription.encode(subscription.getAuthenticationFormat(), data)
xml.append(self.SyncMLHeader(
subscription.incrementSessionId(),
subscription.incrementMessageId(),
subscription.getPublicationUrl(),
subscription.getSubscriptionUrl(),
source_name=subscription.getLogin(),
dataCred=data,
authentication_format=subscription.getAuthenticationFormat(),
authentication_type=subscription.getAuthenticationType()))
# syncml body
sync_body = E.SyncBody()
xml.append(sync_body)
# We have to set every object as NOT_SYNCHRONIZED
subscription.startSynchronization()
# alert message
sync_body.append(self.SyncMLAlert(cmd_id, subscription.getSynchronizationType(),
subscription.getTargetURI(),
subscription.getSourceURI(),
subscription.getLastAnchor(),
subscription.getNextAnchor()))
cmd_id += 1
syncml_put = self.SyncMLPut(cmd_id, subscription)
if syncml_put is not None:
sync_body.append(syncml_put)
cmd_id += 1
sync_body.append(E.Final())
xml_string = etree.tostring(xml, encoding='utf-8', xml_declaration=True,
pretty_print=True)
self.sendResponse(from_url=subscription.subscription_url,
to_url=subscription.publication_url,
sync_id=subscription.getTitle(),
xml=xml_string, domain=subscription,
content_type=subscription.getSyncContentType())
return {'has_response':1, 'xml':xml_string}
def SubSyncModif(self, subscription, xml_client):
"""
Send the client modification, this happens after the Synchronization
initialization
"""
return self.SyncModif(subscription, xml_client)
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Products.ERP5Type.Accessor.TypeDefinition import list_types
from Products.ERP5Type.Globals import Persistent
import re
SYNCML_NAMESPACE = 'SYNCML:SYNCML1.2'
class SyncCode(Persistent):
"""
Class giving the Synchronization's Constants
"""
# SyncML Alert Codes
TWO_WAY = 200
SLOW_SYNC = 201 # This means we get the data from the publication
ONE_WAY_FROM_SERVER = 204
CODE_LIST = ( TWO_WAY, ONE_WAY_FROM_SERVER, )
# SyncML Status Codes
SUCCESS = 200
ITEM_ADDED = 201
WAITING_DATA = 214
REFRESH_REQUIRED = 508
CHUNK_OK = 214
CONFLICT = 409 # A conflict is detected
CONFLICT_MERGE = 207 # We have merged the two versions, sending
# whatever is needed to change(replace)
CONFLICT_CLIENT_WIN = 208 # The client is the "winner", we keep
# the version of the client
UNAUTHORIZED = 401
AUTH_REQUIRED = 407
AUTH_ACCEPTED = 212
# Difference between publication and subscription
PUB = 1
SUB = 0
NULL_ANCHOR = '00000000T000000Z'
# ERP5 Sync Codes
SYNCHRONIZED = 1
SENT = 2
NOT_SENT = 3
PARTIAL = 4
NOT_SYNCHRONIZED = 5
PUB_CONFLICT_MERGE = 6
PUB_CONFLICT_CLIENT_WIN = 8
MAX_LINES = 5000
MAX_OBJECTS = 300
action_tag = 'workflow_action'
#NOT_EDITABLE_PROPERTY = ('id','object','uid','xupdate:element',action_tag,
# 'xupdate:attribute','local_role')
XUPDATE_INSERT = ('xupdate:insert-after','xupdate:insert-before')
XUPDATE_ADD = ('xupdate:append',)
XUPDATE_DEL = ('xupdate:remove',)
XUPDATE_UPDATE = ('xupdate:update',)
XUPDATE_ELEMENT = ('xupdate:element',)
XUPDATE_INSERT_OR_ADD = tuple(XUPDATE_INSERT) + tuple(XUPDATE_ADD)
XUPDATE_TAG = tuple(XUPDATE_INSERT) + tuple(XUPDATE_ADD) + \
tuple(XUPDATE_UPDATE) + tuple(XUPDATE_DEL)
text_type_list = ('text','string')
list_type_list = list_types
none_type = 'None'
boolean_type = 'boolean'
force_conflict_list = ('layout_and_schema','ModificationDate')
binary_type_list = ('image','file','document','pickle')
date_type_list = ('date',)
dict_type_list = ('dict',)
int_type_list = ('int',)
pickle_type_list = ('object',)
data_type_list = ('data', 'base_data',)
xml_object_tag = 'object'
#history_tag = 'workflow_history'
history_tag = 'workflow_action'
local_role_tag = 'local_role'
local_permission_tag = 'local_permission'
local_permission_list = (local_permission_tag,'/'+local_permission_tag)
local_group_tag = 'local_group'
local_role_list = (local_role_tag,'/'+local_role_tag,
local_group_tag,'/'+local_group_tag)
ADDABLE_PROPERTY = local_role_list + (history_tag,) + local_permission_list
NOT_EDITABLE_PROPERTY = ('id','object','uid','xupdate:attribute') \
+ XUPDATE_ELEMENT + ADDABLE_PROPERTY
attribute_type_exp = re.compile("^.*attribute::type$")
history_exp = re.compile("/%s\[@id='.*'\]" % history_tag)
bad_history_exp = re.compile("/%s\[@id='.*'\]/" % history_tag)
extract_id_from_xpath = re.compile(
"(?P<object_block>(?P<property>[^/]+)\[@"\
"(?P<id_of_id>id|gid)='(?P<object_id>[^']+)'\])")
# Those regular expression are deprecated and keept
# only for backward compatibility
object_exp = re.compile("/object\[@id='.*'\]")
sub_object_exp = re.compile("/object\[@id='.*'\]/")
sub_sub_object_exp = re.compile("/object\[@id='.*'\]/object\[@id='.*'\]/")
#media types :
MEDIA_TYPE = {}
MEDIA_TYPE['TEXT_XML'] = 'text/xml'
MEDIA_TYPE['TEXT_VCARD'] = 'text/vcard'
MEDIA_TYPE['TEXT_XVCARD'] = 'text/x-vcard'
#content types :
CONTENT_TYPE = {}
CONTENT_TYPE['SYNCML_XML'] = 'application/vnd.syncml+xml'
CONTENT_TYPE['SYNCML_WBXML'] = 'application/vnd.syncml+wbxml'
#Activity priority
PRIORITY = 5
#Namespace
#In SyncML Representation Protocol OMA
#we use URN as format of namespace
# List namespaces supported
URN_LIST = ('SYNCML:SYNCML1.1', 'SYNCML:SYNCML1.2')
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Products.ERP5Type.Globals import Persistent
import re
# Namespaces.
SYNCML_NAMESPACE = 'SYNCML:SYNCML1.2'
# In SyncML Representation Protocol OMA
# we use URN as format of namespace
# List namespaces supported
URN_LIST = ('SYNCML:SYNCML1.1', 'SYNCML:SYNCML1.2')
NSMAP = {'syncml': SYNCML_NAMESPACE}
## SyncML Alert Codes
#TWO_WAY = 200
#SLOW_SYNC = 201 # This means we get the data from the publication
#ONE_WAY_FROM_SERVER = 204
#CODE_LIST = (TWO_WAY, ONE_WAY_FROM_SERVER,)
# SyncML Status Codes
#SUCCESS = 200
#ITEM_ADDED = 201
#WAITING_DATA = 214
#REFRESH_REQUIRED = 508
#CHUNK_OK = 214
#CONFLICT = 409 # A conflict is detected
#CONFLICT_MERGE = 207 # We have merged the two versions, sending
## whatever is needed to change(replace)
#CONFLICT_CLIENT_WIN = 208 # The client is the "winner", we keep
## the version of the client
#UNAUTHORIZED = 401
#AUTH_REQUIRED = 407
#AUTH_ACCEPTED = 212
NULL_ANCHOR = '00000000T000000Z'
# ERP5 Sync Codes for Signatures
SYNCHRONIZED = 1
#SENT = 2
#NOT_SENT = 3
PARTIAL = 4
NOT_SYNCHRONIZED = 5
PUB_CONFLICT_MERGE = 6
PUB_CONFLICT_CLIENT_WIN = 8
#MAX_LINES = 5000
MAX_OBJECTS = 300
MAX_LEN = 1<<16
XUPDATE_INSERT_LIST = ('xupdate:insert-after', 'xupdate:insert-before')
XUPDATE_ADD = 'xupdate:append'
XUPDATE_DEL = 'xupdate:remove'
XUPDATE_UPDATE = 'xupdate:update'
XUPDATE_ELEMENT = 'xupdate:element'
XUPDATE_INSERT_OR_ADD_LIST = XUPDATE_INSERT_LIST + (XUPDATE_ADD,)
ADD_ACTION = 'Add'
REPLACE_ACTION = 'Replace'
##media types :
#MEDIA_TYPE = {}
#MEDIA_TYPE['TEXT_XML'] = 'text/xml'
#MEDIA_TYPE['TEXT_VCARD'] = 'text/vcard'
#MEDIA_TYPE['TEXT_XVCARD'] = 'text/x-vcard'
##content types :
#CONTENT_TYPE = {}
#CONTENT_TYPE['SYNCML_XML'] = 'application/vnd.syncml+xml'
#CONTENT_TYPE['SYNCML_WBXML'] = 'application/vnd.syncml+wbxml'
#Activity priority
ACTIVITY_PRIORITY = 5
# -*- coding: utf-8 -*-
## Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
"""
ERP portal_synchronizations tool.
"""
from OFS.SimpleItem import SimpleItem
from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Type.Base import Base
from Products.CMFCore.utils import UniqueObject
from Products.ERP5Type.Globals import InitializeClass, DTMLFile, PersistentMapping, Persistent
from AccessControl import ClassSecurityInfo, getSecurityManager
from Products.CMFCore import permissions as CMFCorePermissions
from Publication import Publication, Subscriber
from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2
from Subscription import Subscription
from Products.ERP5Type import Permissions
from PublicationSynchronization import PublicationSynchronization
from SubscriptionSynchronization import SubscriptionSynchronization
from SyncCode import SyncCode
from Products.CMFCore.utils import getToolByName
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager
from AccessControl.User import UnrestrictedUser
from Acquisition import aq_base
import urllib
import urllib2
import httplib
import socket
import os
import string
import commands
import random
from DateTime import DateTime
from zLOG import LOG, TRACE, DEBUG, INFO
from lxml import etree
parser = etree.XMLParser(remove_blank_text=True)
class TimeoutHTTPConnection(httplib.HTTPConnection):
"""
Custom Classes to set timeOut on handle sockets
"""
def connect(self):
httplib.HTTPConnection.connect(self)
self.sock.settimeout(3600)
class TimeoutHTTPHandler(urllib2.HTTPHandler):
def http_open(self, req):
return self.do_open(TimeoutHTTPConnection, req)
class SynchronizationTool( SubscriptionSynchronization,
PublicationSynchronization, UniqueObject, Folder):
"""
This tool implements the synchronization algorithm
TODO: XXX-Please use BaseTool
"""
id = 'portal_synchronizations'
meta_type = 'ERP5 Synchronizations'
portal_type = 'Synchronization Tool'
# On the server, this is use to keep track of the temporary
# copies.
objectsToRemove = []
security = ClassSecurityInfo()
#
# Default values.
#
list_publications = PersistentMapping()
list_subscriptions = PersistentMapping()
# Do we want to use emails ?
#email = None
email = 1
same_export = 1
# Multiple inheritance inconsistency caused by Base must be circumvented
def __init__( self, *args, **kwargs ):
Folder.__init__(self, self.id, **kwargs)
#
# ZMI methods
#
manage_options = ( ( { 'label' : 'Overview'
, 'action' : 'manage_overview'
}
, { 'label' : 'Publications'
, 'action' : 'managePublications'
}
, { 'label' : 'Subscriptions'
, 'action' : 'manageSubscriptions'
}
, { 'label' : 'Conflicts'
, 'action' : 'manageConflicts'
}
)
+ Folder.manage_options
)
security.declareProtected( CMFCorePermissions.ManagePortal
, 'manage_overview' )
manage_overview = DTMLFile( 'dtml/explainSynchronizationTool', globals() )
security.declareProtected( CMFCorePermissions.ManagePortal
, 'managePublications' )
managePublications = DTMLFile( 'dtml/managePublications', globals() )
security.declareProtected( CMFCorePermissions.ManagePortal
, 'manage_addPublicationForm' )
manage_addPublicationForm = DTMLFile( 'dtml/manage_addPublication', globals() )
security.declareProtected( CMFCorePermissions.ManagePortal
, 'manageSubscriptions' )
manageSubscriptions = DTMLFile( 'dtml/manageSubscriptions', globals() )
security.declareProtected( CMFCorePermissions.ManagePortal
, 'manageConflicts' )
manageConflicts = DTMLFile( 'dtml/manageConflicts', globals() )
security.declareProtected( CMFCorePermissions.ManagePortal
, 'manage_addSubscriptionForm' )
manage_addSubscriptionForm = DTMLFile( 'dtml/manage_addSubscription', globals() )
security.declareProtected( CMFCorePermissions.ManagePortal
, 'editProperties' )
def editProperties( self
, publisher=None
, REQUEST=None
):
"""
Form handler for "tool-wide" properties (including list of
metadata elements).
"""
if publisher is not None:
self.publisher = publisher
if REQUEST is not None:
REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
+ '/propertiesForm'
+ '?manage_tabs_message=Tool+updated.'
)
security.declareProtected(Permissions.ModifyPortalContent,
'manage_addPublication')
def manage_addPublication(self, title, publication_url,
destination_path, source_uri, query, xml_mapping,
conduit, gpg_key,
synchronization_id_generator=None,
media_type=None, authentication_format='b64',
authentication_type='syncml:auth-basic',
RESPONSE=None, activity_enabled = False,
sync_content_type='application/vnd.syncml+xml',
synchronize_with_erp5_sites=True):
"""
create a new publication
"""
folder = self.getObjectContainer()
new_id = self.getPublicationIdFromTitle(title)
pub = Publication(new_id, title, publication_url,
destination_path, source_uri, query, xml_mapping,
conduit, gpg_key, synchronization_id_generator,
media_type,
authentication_format,
authentication_type,
activity_enabled, synchronize_with_erp5_sites,
sync_content_type)
folder._setObject( new_id, pub )
if RESPONSE is not None:
RESPONSE.redirect('managePublications')
security.declareProtected(Permissions.ModifyPortalContent,
'manage_addSubscription')
def manage_addSubscription(self, title, publication_url, subscription_url,
destination_path, source_uri, target_uri, query,
xml_mapping, conduit, gpg_key,
synchronization_id_generator=None,
media_type=None, login=None, password=None,
RESPONSE=None, activity_enabled=False,
alert_code=SyncCode.TWO_WAY,
synchronize_with_erp5_sites = True,
sync_content_type='application/vnd.syncml+xml'):
"""
XXX should be renamed as addSubscription
create a new subscription
"""
folder = self.getObjectContainer()
new_id = self.getSubscriptionIdFromTitle(title)
sub = Subscription(new_id, title, publication_url, subscription_url,
destination_path, source_uri, target_uri, query,
xml_mapping, conduit, gpg_key,
synchronization_id_generator, media_type,
login, password, activity_enabled, alert_code,
synchronize_with_erp5_sites, sync_content_type)
folder._setObject( new_id, sub )
if RESPONSE is not None:
RESPONSE.redirect('manageSubscriptions')
security.declareProtected(Permissions.ModifyPortalContent,
'manage_editPublication')
def manage_editPublication(self, title, publication_url,
destination_path, source_uri, query, xml_mapping,
conduit, gpg_key, synchronization_id_generator,
media_type=None,
authentication_format='b64',
authentication_type='syncml:auth-basic',
RESPONSE=None, activity_enabled=False,
sync_content_type='application/vnd.syncml+xml',
synchronize_with_erp5_sites=False):
"""
modify a publication
"""
pub = self.getPublication(title)
pub.setTitle(title)
pub.setActivityEnabled(activity_enabled)
pub.setPublicationUrl(publication_url)
pub.setDestinationPath(destination_path)
pub.setSourceURI(source_uri)
pub.setQuery(query)
pub.setConduit(conduit)
pub.setXMLMapping(xml_mapping)
pub.setGPGKey(gpg_key)
pub.setSynchronizationIdGenerator(synchronization_id_generator)
pub.setMediaType(media_type)
pub.setAuthenticationFormat(authentication_format)
pub.setAuthenticationType(authentication_type)
pub.setSyncContentType(sync_content_type)
pub.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites)
if RESPONSE is not None:
RESPONSE.redirect('managePublications')
security.declareProtected(Permissions.ModifyPortalContent,
'manage_editSubscription')
def manage_editSubscription(self, title, publication_url, subscription_url,
destination_path, source_uri, target_uri, query, xml_mapping, conduit,
gpg_key, synchronization_id_generator, media_type=None,
login='', password='', RESPONSE=None, activity_enabled=False,
alert_code=SyncCode.TWO_WAY, synchronize_with_erp5_sites=False,
sync_content_type='application/vnd.syncml+xml'):
"""
modify a subscription
"""
sub = self.getSubscription(title)
sub.setTitle(title)
sub.setActivityEnabled(activity_enabled)
sub.setPublicationUrl(publication_url)
sub.setDestinationPath(destination_path)
sub.setSourceURI(source_uri)
sub.setTargetURI(target_uri)
sub.setQuery(query)
sub.setConduit(conduit)
sub.setXMLMapping(xml_mapping)
sub.setGPGKey(gpg_key)
sub.setSubscriptionUrl(subscription_url)
sub.setSynchronizationIdGenerator(synchronization_id_generator)
sub.setMediaType(media_type)
sub.setLogin(login)
sub.setPassword(password)
sub.setSyncContentType(sync_content_type)
sub.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites)
sub.setAlertCode(alert_code)
if RESPONSE is not None:
RESPONSE.redirect('manageSubscriptions')
security.declareProtected(Permissions.ModifyPortalContent,
'manage_deletePublication')
def manage_deletePublication(self, title, RESPONSE=None):
"""
delete a publication
"""
id = self.getPublicationIdFromTitle(title)
folder = self.getObjectContainer()
folder._delObject(id)
if RESPONSE is not None:
RESPONSE.redirect('managePublications')
security.declareProtected(Permissions.ModifyPortalContent,
'manage_deleteSubscription')
def manage_deleteSubscription(self, title, RESPONSE=None):
"""
delete a subscription
"""
id = self.getSubscriptionIdFromTitle(title)
folder = self.getObjectContainer()
folder._delObject(id)
if RESPONSE is not None:
RESPONSE.redirect('manageSubscriptions')
security.declareProtected(Permissions.ModifyPortalContent,
'manage_resetPublication')
def manage_resetPublication(self, title, RESPONSE=None):
"""
reset a publication
"""
pub = self.getPublication(title)
pub.resetAllSubscribers()
if RESPONSE is not None:
RESPONSE.redirect('managePublications')
security.declareProtected(Permissions.ModifyPortalContent,
'manage_resetSubscription')
def manage_resetSubscription(self, title, RESPONSE=None):
"""
reset a subscription
"""
sub = self.getSubscription(title)
sub.resetAllSignatures()
sub.resetAnchors()
if RESPONSE is not None:
RESPONSE.redirect('manageSubscriptions')
security.declareProtected(Permissions.ModifyPortalContent,
'manage_syncSubscription')
def manage_syncSubscription(self, title, RESPONSE=None):
"""
reset a subscription
"""
LOG('Synchronisation Subscription', INFO, 'Starting ...')
self.SubSync(self.getSubscription(title).getPath())
if RESPONSE is not None:
RESPONSE.redirect('manageSubscriptions')
security.declareProtected(Permissions.AccessContentsInformation,
'getPublicationList')
def getPublicationList(self):
"""
Return a list of publications
"""
folder = self.getObjectContainer()
return [pub for pub in folder.objectValues() if pub.getDomainType() == self.PUB]
security.declareProtected(Permissions.AccessContentsInformation,
'getPublication')
def getPublication(self, title):
"""
Return the publications with this id
"""
pub = None
for p in self.getPublicationList():
if p.getTitle() == title:
pub = p
break
return pub
security.declareProtected(Permissions.AccessContentsInformation,
'getObjectContainer')
def getObjectContainer(self):
"""
this returns the external mount point if there is one
"""
folder = self
portal_url = getToolByName(self,'portal_url')
root = portal_url.getPortalObject().aq_parent
if 'external_mount_point' in root.objectIds():
folder = root.external_mount_point
return folder
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriptionList')
def getSubscriptionList(self):
"""
Return a list of publications
"""
folder = self.getObjectContainer()
return [sub for sub in folder.objectValues() if sub.getDomainType() == self.SUB]
def getSubscription(self, title):
"""
Returns the subscription with this title
"""
sub = None
for s in self.getSubscriptionList():
if s.getTitle() == title:
sub = s
return sub
security.declareProtected(Permissions.AccessContentsInformation,
'getSynchronizationList')
def getSynchronizationList(self):
"""
Returns the list of subscriptions and publications
"""
return self.getSubscriptionList() + self.getPublicationList()
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriberList')
def getSubscriberList(self):
"""
Returns the list of subscribers and subscriptions
"""
s_list = []
s_list.extend(self.getSubscriptionList())
for publication in self.getPublicationList():
s_list.extend(publication.getSubscriberList())
return s_list
security.declareProtected(Permissions.AccessContentsInformation,
'getConflictList')
def getConflictList(self, context=None):
"""
Retrieve the list of all conflicts
Here the list is as follow :
[conflict_1,conflict2,...] where conflict_1 is like:
['publication',publication_id,object.getPath(),property_id,
publisher_value,subscriber_value]
"""
path = self.resolveContext(context)
conflict_list = []
for publication in self.getPublicationList():
for subscriber in publication.getSubscriberList():
sub_conflict_list = subscriber.getConflictList()
for conflict in sub_conflict_list:
conflict.setSubscriber(subscriber)
if path is None or conflict.getObjectPath() == path:
conflict_list += [conflict.__of__(subscriber)]
for subscription in self.getSubscriptionList():
sub_conflict_list = subscription.getConflictList()
#LOG('SynchronizationTool.getConflictList, sub_conflict_list', DEBUG,
#sub_conflict_list)
for conflict in sub_conflict_list:
conflict.setSubscriber(subscription)
if path is None or conflict.getObjectPath() == path:
conflict_list += [conflict.__of__(subscription)]
return conflict_list
security.declareProtected(Permissions.AccessContentsInformation,
'getDocumentConflictList')
def getDocumentConflictList(self, context=None):
"""
Retrieve the list of all conflicts for a given document
Well, this is the same thing as getConflictList with a path
"""
return self.getConflictList(context)
security.declareProtected(Permissions.AccessContentsInformation,
'getSynchronizationState')
def getSynchronizationState(self, context):
"""
context : the context on which we are looking for state
This functions have to retrieve the synchronization state,
it will first look in the conflict list, if nothing is found,
then we have to check on a publication/subscription.
This method returns a mapping between subscription and states
JPS suggestion:
path -> object, document, context, etc.
type -> '/titi/toto' or ('','titi', 'toto') or <Base instance 1562567>
object = self.resolveContext(context) (method to add)
"""
path = self.resolveContext(context)
conflict_list = self.getConflictList()
state_list= []
#LOG('getSynchronizationState', DEBUG, 'path: %s' % str(path))
for conflict in conflict_list:
if conflict.getObjectPath() == path:
#LOG('getSynchronizationState', DEBUG, 'found a conflict: %s' % str(conflict))
state_list.append([conflict.getSubscriber(), self.CONFLICT])
for domain in self.getSynchronizationList():
destination = domain.getDestinationPath()
#LOG('getSynchronizationState', TRACE, 'destination: %s' % str(destination))
j_path = '/'.join(path)
#LOG('getSynchronizationState', TRACE, 'j_path: %s' % str(j_path))
if j_path.find(destination)==0:
o_id = j_path[len(destination)+1:].split('/')[0]
#LOG('getSynchronizationState', TRACE, 'o_id: %s' % o_id)
if domain.domain_type==self.PUB:
subscriber_list = domain.getSubscriberList()
else:
subscriber_list = [domain]
#LOG('getSynchronizationState, subscriber_list:', TRACE, subscriber_list)
for subscriber in subscriber_list:
signature = subscriber.getSignatureFromObjectId(o_id)
#XXX check if signature could be not None ...
if signature is not None:
state = signature.getStatus()
#LOG('getSynchronizationState:', TRACE, 'sub.dest :%s, state: %s' % \
#(subscriber.getSubscriptionUrl(),str(state)))
found = False
# Make sure there is not already a conflict giving the state
for state_item in state_list:
if state_item[0] == subscriber:
found = True
if not found:
state_list.append([subscriber, state])
return state_list
security.declareProtected(Permissions.AccessContentsInformation,
'getAlertCodeList')
def getAlertCodeList(self):
return self.CODE_LIST
security.declareProtected(Permissions.ModifyPortalContent,
'applyPublisherValue')
def applyPublisherValue(self, conflict):
"""
after a conflict resolution, we have decided
to keep the local version of an object
"""
object = self.unrestrictedTraverse(conflict.getObjectPath())
subscriber = conflict.getSubscriber()
# get the signature:
#LOG('p_sync.applyPublisherValue, subscriber: ', DEBUG, subscriber)
signature = subscriber.getSignatureFromObjectId(object.getId()) # XXX may be change for rid
copy_path = conflict.getCopyPath()
signature.delConflict(conflict)
if not signature.getConflictList():
if copy_path is not None:
#LOG('p_sync.applyPublisherValue, conflict_list empty on : ', TRACE, signature)
# Delete the copy of the object if the there is one
directory = object.getParentValue()
copy_id = copy_path[-1]
#LOG('p_sync.applyPublisherValue, copy_id: ', TRACE, copy_id)
if getattr(aq_base(directory), 'hasObject', None) is not None:
# optimize the case of a BTree folder
#LOG('p_sync.applyPublisherValue, deleting...: ', TRACE, copy_id)
if directory.hasObject(copy_id):
directory._delObject(copy_id)
elif copy_id in directory.objectIds():
directory._delObject(copy_id)
signature.setStatus(self.PUB_CONFLICT_MERGE)
security.declareProtected(Permissions.ModifyPortalContent,
'applyPublisherDocument')
def applyPublisherDocument(self, conflict):
"""
apply the publisher value for all conflict of the given document
"""
subscriber = conflict.getSubscriber()
for c in self.getConflictList(conflict.getObjectPath()):
if c.getSubscriber() == subscriber:
#LOG('applyPublisherDocument, applying on conflict: ', DEBUG, conflict)
c.applyPublisherValue()
security.declareProtected(Permissions.AccessContentsInformation,
'getPublisherDocumentPath')
def getPublisherDocumentPath(self, conflict):
"""
apply the publisher value for all conflict of the given document
"""
subscriber = conflict.getSubscriber()
return conflict.getObjectPath()
security.declareProtected(Permissions.AccessContentsInformation,
'getPublisherDocument')
def getPublisherDocument(self, conflict):
"""
apply the publisher value for all conflict of the given document
"""
publisher_object_path = self.getPublisherDocumentPath(conflict)
#LOG('getPublisherDocument publisher_object_path', TRACE, publisher_object_path)
publisher_object = self.unrestrictedTraverse(publisher_object_path)
return publisher_object
def getSubscriberDocumentVersion(self, conflict, docid):
"""
Given a 'conflict' and a 'docid' refering to a new version of a
document, applies the conflicting changes to the document's new
version. By so, two differents versions of the same document will be
available.
Thus, the manager will be able to open both version of the document
before selecting which one to keep.
"""
subscriber = conflict.getSubscriber()
publisher_object_path = conflict.getObjectPath()
publisher_object = self.unrestrictedTraverse(publisher_object_path)
publisher_xml = self.getXMLObject(
object=publisher_object,
xml_mapping=subscriber.getXMLMapping())
directory = publisher_object.aq_parent
object_id = docid
if object_id in directory.objectIds():
directory._delObject(object_id)
# Import the conduit and get it
conduit_name = subscriber.getConduit()
conduit = self.getConduitByName(conduit_name)
conduit.addNode(
xml=publisher_xml,
object=directory,
object_id=object_id)
subscriber_document = directory._getOb(object_id)
for c in self.getConflictList(conflict.getObjectPath()):
if c.getSubscriber() == subscriber:
c.applySubscriberValue(object=subscriber_document)
return subscriber_document
def _getCopyId(self, object):
directory = object.aq_inner.aq_parent
if directory.getId() != 'portal_repository':
object_id = object.getId() + '_conflict_copy'
if object_id in directory.objectIds():
directory._delObject(object_id)
else:
repotool = directory
docid, rev = repotool.getDocidAndRevisionFromObjectId(object.getId())
new_rev = repotool.getFreeRevision(docid) + 10 # make sure it's not gonna provoke conflicts
object_id = repotool._getId(docid, new_rev)
return object_id
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriberDocumentPath')
def getSubscriberDocumentPath(self, conflict):
"""
apply the publisher value for all conflict of the given document
"""
copy_path = conflict.getCopyPath()
if copy_path is not None:
return copy_path
subscriber = conflict.getSubscriber()
publisher_object_path = conflict.getObjectPath()
publisher_object = self.unrestrictedTraverse(publisher_object_path)
conduit_name = subscriber.getConduit()
conduit = self.getConduitByName(conduit_name)
publisher_xml = conduit.getXMLFromObjectWithId(publisher_object,\
xml_mapping=subscriber.getXMLMapping())
directory = publisher_object.aq_inner.aq_parent
object_id = self._getCopyId(publisher_object)
# Import the conduit and get it
conduit.addNode(
xml=publisher_xml,
object=directory,
object_id=object_id)
subscriber_document = directory._getOb(object_id)
subscriber_document._conflict_resolution = 1
for c in self.getConflictList(conflict.getObjectPath()):
if c.getSubscriber() == subscriber:
c.applySubscriberValue(object=subscriber_document)
copy_path = subscriber_document.getPhysicalPath()
conflict.setCopyPath(copy_path)
return copy_path
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriberDocument')
def getSubscriberDocument(self, conflict):
"""
apply the publisher value for all conflict of the given document
"""
subscriber_object_path = self.getSubscriberDocumentPath(conflict)
subscriber_object = self.unrestrictedTraverse(subscriber_object_path)
return subscriber_object
security.declareProtected(Permissions.ModifyPortalContent,
'applySubscriberDocument')
def applySubscriberDocument(self, conflict):
"""
apply the subscriber value for all conflict of the given document
"""
subscriber = conflict.getSubscriber()
for c in self.getConflictList(conflict.getObjectPath()):
if c.getSubscriber() == subscriber:
c.applySubscriberValue()
security.declareProtected(Permissions.ModifyPortalContent,
'applySubscriberValue')
def applySubscriberValue(self, conflict,object=None):
"""
after a conflict resolution, we have decided
to keep the local version of an object
"""
solve_conflict = 1
if object is None:
object = self.unrestrictedTraverse(conflict.getObjectPath())
else:
# This means an object was given, this is used in order
# to see change on a copy, so don't solve conflict
solve_conflict=0
subscriber = conflict.getSubscriber()
# get the signature:
signature = subscriber.getSignatureFromObjectId(object.getId()) # XXX may be change for rid
# Import the conduit and get it
conduit_name = subscriber.getConduit()
conduit = self.getConduitByName(conduit_name)
for xupdate in conflict.getXupdateList():
conduit.updateNode(xml=xupdate, object=object, force=1)
if solve_conflict:
copy_path = conflict.getCopyPath()
signature.delConflict(conflict)
if not signature.getConflictList():
if copy_path is not None:
# Delete the copy of the object if the there is one
directory = object.aq_parent
copy_id = copy_path[-1]
if getattr(directory.aq_base, 'hasObject', None) is not None:
# optimize the case of a BTree folder
if directory.hasObject(id):
directory._delObject(copy_id)
elif copy_id in directory.objectIds():
directory._delObject(copy_id)
signature.setStatus(self.PUB_CONFLICT_MERGE)
security.declareProtected(Permissions.ModifyPortalContent,
'managePublisherValue')
def managePublisherValue(self, subscription_url, property_id, object_path,
RESPONSE=None):
"""
Do whatever needed in order to store the local value on
the remote server
Suggestion (API)
add method to view document with applied xupdate
of a given subscriber XX
(ex. viewSubscriberDocument?path=ddd&subscriber_id=dddd)
Version=Version CPS
"""
# Retrieve the conflict object
#LOG('manageLocalValue', DEBUG, '%s %s %s' % (str(subscription_url),
#str(property_id),
#str(object_path)))
for conflict in self.getConflictList():
if conflict.getPropertyId() == property_id:
if '/'.join(conflict.getObjectPath()) == object_path:
if conflict.getSubscriber().getSubscriptionUrl() == subscription_url:
conflict.applyPublisherValue()
if RESPONSE is not None:
RESPONSE.redirect('manageConflicts')
security.declareProtected(Permissions.ModifyPortalContent,
'manageSubscriberValue')
def manageSubscriberValue(self, subscription_url, property_id, object_path,
RESPONSE=None):
"""
Do whatever needed in order to store the remote value locally
and confirmed that the remote box should keep it's value
"""
#LOG('manageLocalValue', DEBUG, '%s %s %s' % (str(subscription_url),
#str(property_id),
#str(object_path)))
for conflict in self.getConflictList():
if conflict.getPropertyId() == property_id:
if '/'.join(conflict.getObjectPath()) == object_path:
if conflict.getSubscriber().getSubscriptionUrl() == subscription_url:
conflict.applySubscriberValue()
if RESPONSE is not None:
RESPONSE.redirect('manageConflicts')
security.declareProtected(Permissions.ModifyPortalContent,
'manageSubscriberDocument')
def manageSubscriberDocument(self, subscription_url, object_path):
"""
"""
for conflict in self.getConflictList():
if '/'.join(conflict.getObjectPath()) == object_path:
if conflict.getSubscriber().getSubscriptionUrl() == subscription_url:
conflict.applySubscriberDocument()
break
self.managePublisherDocument(object_path)
security.declareProtected(Permissions.ModifyPortalContent,
'managePublisherDocument')
def managePublisherDocument(self, object_path):
"""
"""
retry = True
while retry:
retry = False
for conflict in self.getConflictList():
if '/'.join(conflict.getObjectPath()) == object_path:
conflict.applyPublisherDocument()
retry = True
break
def resolveContext(self, context):
"""
We try to return a path (like ('','erp5','foo') from the context.
Context can be :
- a path
- an object
- a string representing a path
"""
if context is None:
return context
elif isinstance(context, tuple):
return context
elif isinstance(context, tuple):
return tuple(context.split('/'))
else:
return context.getPhysicalPath()
security.declarePublic('sendResponse')
def sendResponse(self, to_url=None, from_url=None, sync_id=None, xml=None,
domain=None, send=1, content_type='application/vnd.syncml+xml'):
"""
We will look at the url and we will see if we need to send mail, http
response, or just copy to a file.
"""
#LOG('sendResponse, self.getPhysicalPath: ', INFO, self.getPhysicalPath())
#LOG('sendResponse, to_url: ', INFO, to_url)
#LOG('sendResponse, from_url: ', INFO, from_url)
#LOG('sendResponse, sync_id: ', INFO, sync_id)
#LOG('sendResponse, xml: \n', INFO, xml)
if content_type == self.CONTENT_TYPE['SYNCML_WBXML']:
xml = self.xml2wbxml(xml)
#LOG('sendHttpResponse, xml after wbxml: \n', DEBUG, self.hexdump(xml))
if domain is not None:
gpg_key = domain.getGPGKey()
if gpg_key not in ('',None):
filename = str(random.randrange(1,2147483600)) + '.txt'
decrypted = file('/tmp/%s' % filename,'w')
decrypted.write(xml)
decrypted.close()
(status,output)=commands.getstatusoutput('gzip /tmp/%s' % filename)
(status,output)=commands.getstatusoutput('gpg --yes --homedir \
/var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" -se \
/tmp/%s.gz' % (gpg_key,filename))
#LOG('readResponse, gpg output:', DEBUG, output)
encrypted = file('/tmp/%s.gz.gpg' % filename,'r')
xml = encrypted.read()
encrypted.close()
commands.getstatusoutput('rm -f /tmp/%s.gz' % filename)
commands.getstatusoutput('rm -f /tmp/%s.gz.gpg' % filename)
if send:
if isinstance(to_url, str):
if to_url.find('http://') == 0:
domain = aq_base(domain)
if domain.domain_type == self.PUB and not domain.getActivityEnabled():
# not use activity
# XXX Make sure this is not a problem
return None
#use activities to send send an http response
#LOG('sendResponse, will start sendHttpResponse, xml', DEBUG, '')
self.activate(activity='SQLQueue',
tag=domain.getId(),
priority=self.PRIORITY).sendHttpResponse(
sync_id=sync_id,
to_url=to_url,
xml=xml,
domain_path=domain.getPath(),
content_type=content_type)
elif to_url.find('file://') == 0:
filename = to_url[len('file:/'):]
stream = file(filename, 'w')
stream.write(xml)
stream.close()
# we have to use local files (unit testing for example
elif to_url.find('mailto:') == 0:
# we will send an email
to_address = to_url[len('mailto:'):]
from_address = from_url[len('mailto:'):]
self.sendMail(from_address, to_address, sync_id, xml)
return xml
security.declarePrivate('sendHttpResponse')
def sendHttpResponse(self, to_url=None, sync_id=None, xml=None,
domain_path=None, content_type='application/vnd.syncml+xml'):
domain = self.unrestrictedTraverse(domain_path)
#LOG('sendHttpResponse, starting with domain:', DEBUG, domain)
if domain is not None:
if domain.domain_type == self.PUB and not domain.getActivityEnabled():
return xml
# Retrieve the proxy from os variables
proxy_url = ''
if os.environ.has_key('http_proxy'):
proxy_url = os.environ['http_proxy']
#LOG('sendHttpResponse, proxy_url:', DEBUG, proxy_url)
if proxy_url !='':
proxy_handler = urllib2.ProxyHandler({"http" :proxy_url})
else:
proxy_handler = urllib2.ProxyHandler({})
pass_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
auth_handler = urllib2.HTTPBasicAuthHandler(pass_mgr)
proxy_auth_handler = urllib2.ProxyBasicAuthHandler(pass_mgr)
opener = urllib2.build_opener(proxy_handler, proxy_auth_handler,
auth_handler, TimeoutHTTPHandler)
urllib2.install_opener(opener)
to_encode = {}
to_encode['text'] = xml
to_encode['sync_id'] = sync_id
headers = {'User-Agent':'ERP5SyncML', 'Content-Type':content_type}
#XXX bad hack for synchronization with erp5
# because at this time, when we call the readResponse method, we must
# encode the data with urlencode if we want the readResponse method to
# receive the data's in parameters.
# All this should be improved to not use urlencode in all cases.
# to do this, perhaps use SOAP :
# - http://en.wikipedia.org/wiki/SOAP
# - http://www.contentmanagementsoftware.info/zope/SOAPSupport
# - http://svn.zope.org/soap/trunk/
if domain.getSynchronizeWithERP5Sites():
#LOG('Synchronization with another ERP5 instance ...', DEBUG, '')
if to_url.find('readResponse')<0:
to_url = to_url + '/portal_synchronizations/readResponse'
encoded = urllib.urlencode(to_encode)
data = encoded
request = urllib2.Request(url=to_url, data=data)
else:
#XXX only to synchronize with other server than erp5 (must be improved):
data = head+xml
request = urllib2.Request(to_url, data, headers)
try:
url_file = urllib2.urlopen(request)
result = url_file.read()
except socket.error, msg:
self.activate(activity='SQLQueue',
tag=domain.getId(),
priority=self.PRIORITY).sendHttpResponse(
to_url=to_url,
sync_id=sync_id,
xml=xml,
domain_path=domain.getPath(),
content_type=content_type)
LOG('sendHttpResponse, socket ERROR:', INFO, msg)
LOG('sendHttpResponse, url, data', INFO, (to_url, data))
return
except urllib2.URLError, msg:
LOG("sendHttpResponse, can't open url %s :" % to_url, INFO, msg)
LOG('sendHttpResponse, to_url, data', INFO, (to_url, data))
return
if domain is not None:
if domain.domain_type == self.SUB and not domain.getActivityEnabled():
#if we don't use activity :
gpg_key = domain.getGPGKey()
if result:
self.readResponse(sync_id=sync_id, text=result)
return result
security.declarePublic('sync')
def sync(self):
"""
This will try to synchronize every subscription
"""
message_list = self.portal_activities.getMessageList()
#LOG('sync, len(message_list):', DEBUG, len(message_list))
if len(message_list) == 0:
for subscription in self.getSubscriptionList():
user_id = subscription.getZopeUser()
uf = self.getPortalObject().acl_users
user = uf.getUserById(user_id).__of__(uf)
newSecurityManager(None, user)
subscription.activate(activity='SQLQueue',
tag=subscription.getId(),
priority=self.PRIORITY
).SubSync(subscription.getPath())
security.declarePublic('readResponse')
def readResponse(self, text='', sync_id=None, to_url=None, from_url=None):
"""
We will look at the url and we will see if we need to send mail, http
response, or just copy to a file.
"""
#LOG('readResponse, text :', DEBUG, text)
#LOG('readResponse, hexdump(text) :', DEBUG, self.hexdump(text))
status_code = None
if text:
# XXX We will look everywhere for a publication/subsription with
# the id sync_id, this is not so good, but there is no way yet
# to know if we will call a publication or subscription XXX
gpg_key = ''
#LOG('readResponse, sync_id :', DEBUG, sync_id)
for publication in self.getPublicationList():
if publication.getTitle() == sync_id:
gpg_key = publication.getGPGKey()
domain = publication
break
if not gpg_key:
for subscription in self.getSubscriptionList():
if subscription.getTitle() == sync_id:
gpg_key = subscription.getGPGKey()
domain = subscription
user = domain.getZopeUser()
#LOG('readResponse, user :', DEBUG, user)
newSecurityManager(None, user)
break
# decrypt the message if needed
else:
filename = str(random.randrange(1, 2147483600)) + '.txt'
encrypted = file('/tmp/%s.gz.gpg' % filename,'w')
encrypted.write(text)
encrypted.close()
(status, output) = commands.getstatusoutput('gpg --homedir \
/var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" --decrypt \
/tmp/%s.gz.gpg > /tmp/%s.gz' % (gpg_key, filename, filename))
LOG('readResponse, gpg output:', TRACE, output)
(status,output)=commands.getstatusoutput('gunzip /tmp/%s.gz' % filename)
decrypted = file('/tmp/%s' % filename,'r')
text = decrypted.read()
LOG('readResponse, text:', TRACE, text)
decrypted.close()
commands.getstatusoutput('rm -f /tmp/%s' % filename)
commands.getstatusoutput('rm -f /tmp/%s.gz.gpg' % filename)
# Get the target and then find the corresponding publication or
# Subscription
#LOG('type(text) : ', TRACE, type(text))
if domain.getSyncContentType() == self.CONTENT_TYPE['SYNCML_WBXML']:
text = self.wbxml2xml(text)
#LOG('readResponse, text after wbxml :\n', TRACE, text)
xml = etree.XML(text, parser=parser)
url = self.getTarget(xml)
for publication in self.getPublicationList():
if publication.getPublicationUrl() == url and \
publication.getTitle() == sync_id:
if publication.getActivityEnabled():
#use activities to send SyncML data.
publication.activate(activity='SQLQueue',
tag=publication.getId(),
priority=self.PRIORITY).PubSync(
publication.getPath(),
text)
return ' '
else:
result = self.PubSync(publication.getPath(), xml)
# Then encrypt the message
xml = result['xml']
if publication.getSyncContentType() ==\
self.CONTENT_TYPE['SYNCML_WBXML']:
xml = self.xml2wbxml(xml)
return xml
for subscription in self.getSubscriptionList():
if subscription.getSubscriptionUrl() == url and \
subscription.getTitle() == sync_id:
subscription_path = subscription.getPath()
self.activate(activity='SQLQueue',
tag=subscription.getId(),
priority=self.PRIORITY).SubSync(
subscription_path,
text)
return ' '
# we use from only if we have a file
elif isinstance(from_url, str):
if from_url.find('file://') == 0:
try:
filename = from_url[len('file:/'):]
stream = file(filename, 'r')
xml = stream.read()
#LOG('readResponse', DEBUG, 'file... msg: %s' % str(stream.read()))
except IOError:
LOG('readResponse, cannot read file: ', INFO, filename)
xml = None
if xml is not None and len(xml) == 0:
xml = None
return xml
security.declareProtected(Permissions.ModifyPortalContent,
'getPublicationIdFromTitle')
def getPublicationIdFromTitle(self, title):
"""
simply return an id from a title
"""
return 'pub_' + title
security.declareProtected(Permissions.ModifyPortalContent,
'getPublicationIdFromTitle')
def getSubscriptionIdFromTitle(self, title):
"""
simply return an id from a title
"""
return 'sub_' + title
security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
def addNode(self, conduit='ERP5Conduit', **kw):
"""
"""
# Import the conduit and get it
conduit_object = self.getConduitByName(conduit)
return conduit_object.addNode(**kw)
def hexdump(self, raw=''):
"""
this function is used to display the raw in a readable format :
it display raw in hexadecimal format and display too the printable
characters (because if not printable characters are printed, it makes
terminal display crash)
"""
buf = ""
line = ""
start = 0
done = False
while not done:
end = start + 16
max = len(str(raw))
if end > max:
end = max
done = True
chunk = raw[start:end]
for i in xrange(len(chunk)):
if i > 0:
spacing = " "
else:
spacing = ""
buf += "%s%02x" % (spacing, ord(chunk[i]))
if done:
for i in xrange(16 - (end % 16)):
buf += " "
buf += " "
for c in chunk:
val = ord(c)
if val >= 33 and val <= 126:
buf += c
else:
buf += "."
buf += "\n"
start += 16
return buf
InitializeClass( SynchronizationTool )
This source diff could not be displayed because it is too large. You can view the blob instead.
# -*- coding: utf-8 -*-
#############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
# Required modules - some modules are imported later to prevent circular deadlocks
import persistent
try:
# Python 2.5 or later
from hashlib import md5 as md5_new
from hashlib import sha1 as sha_new
except ImportError:
# Python 2.4
from md5 import new as md5_new
from sha import new as sha_new
#####################################################
# Avoid importing from (possibly unpatched) Globals
#####################################################
from ZPublisher.HTTPRequest import FileUpload
from OFS.Image import Pdata
from StringIO import StringIO
import transaction
class PdataHelper(persistent.Persistent):
"""Inspired by OFS.Image, this wrapper aim to handle
long string easily to transform them into Pdata
"""
def __init__(self, persistent_object, value):
"""Constructor
- persistent_object: Object contains by storage to access it.
- value: value to wrapp into Pdata if it is a BaseString or a file.
It can be also a Pdata object
"""
self._max_len = 1 << 16
self._data, self.size = self._read_data(persistent_object, value)
self.md5sum = None
def _read_data(self, persistent_object, value):
"""Copied from OFS.Image._read_data
with some modernisation.
Returns always a Pdata and its size
- persistent_object: Object known by storage to access it.
- value: value to wrapp into Pdata
"""
n = self._max_len
if isinstance(value, (str, unicode)):
if isinstance(value, unicode):
value = value.encode('utf-8')
size=len(value)
if size < n:
return Pdata(value), size
# Big string: cut it into smaller chunks
value = StringIO(value)
if isinstance(value, FileUpload) and not value:
raise ValueError, 'File not specified'
if isinstance(value, Pdata):
size = self._read_size_from_pdata(value)
return value, size
# Clear md5sum to force refreshing
self.md5sum = None
seek=value.seek
read=value.read
seek(0,2)
size=end=value.tell()
if size <= 2*n:
seek(0)
return Pdata(read(size)), size
# Make sure we have an _p_jar, even if we are a new object, by
# doing a sub-transaction commit.
transaction.savepoint(optimistic=True)
if persistent_object._p_jar is None:
# Ugh
seek(0)
return Pdata(read(size)), size
# Now we're going to build a linked list from back
# to front to minimize the number of database updates
# and to allow us to get things out of memory as soon as
# possible.
next = None
while end > 0:
pos = end-n
if pos < n:
pos = 0 # we always want at least n bytes
seek(pos)
# Create the object and assign it a next pointer
# in the same transaction, so that there is only
# a single database update for it.
data = Pdata(read(end-pos))
persistent_object._p_jar.add(data)
data.next = next
# Save the object so that we can release its memory.
transaction.savepoint(optimistic=True)
data._p_deactivate()
# The object should be assigned an oid and be a ghost.
assert data._p_oid is not None
assert data._p_state == -1
next = data
end = pos
return next, size
def _digest_md5_hash_from_pdata(self, pdata):
"""Compute hash part by part
"""
md5_hash = md5_new()
next = pdata
while next is not None:
md5_hash.update(next.data)
next = next.next
return md5_hash.hexdigest()
def _read_size_from_pdata(self, pdata):
"""Compute size part by part
"""
size = 0
next = pdata
while next is not None:
size += len(next.data)
next = next.next
return size
def __len__(self):
"""Return size of Pdata value
"""
return self.size
def __str__(self):
"""Return string concatenation
of all Pdata parts
"""
return str(self._data)
def getContentMd5(self):
"""
"""
if self.md5sum is not None:
return self.md5sum
md5sum = self._digest_md5_hash_from_pdata(self._data)
self.md5sum = md5sum
return md5sum
def __getslice__(self, i, j):
"""XXX Could be improved to avoid loading
into memory all Pdata objects
"""
return self.__str__()[i:j]
def getLastPdata(self):
"""return the last Pdata element
of a Pdata chains
"""
pdata = self._data
next = pdata.next
while next is not None:
pdata = next
next = pdata.next
return pdata
......@@ -28,264 +28,247 @@
##############################################################################
import smtplib
from Products.ERP5SyncML.SyncCode import SyncCode
from Products.ERP5SyncML.Signature import Signature
from AccessControl.SecurityManagement import newSecurityManager
from App.config import getConfiguration
from Products.CMFCore.utils import getToolByName
from ERP5Diff import ERP5Diff
from zLOG import LOG, INFO
from lxml import etree
from lxml.etree import Element
from lxml.builder import ElementMaker
from SyncCode import SYNCML_NAMESPACE
nsmap = {'syncml' : SYNCML_NAMESPACE}
E = ElementMaker(namespace=SYNCML_NAMESPACE, nsmap=nsmap)
from SyncMLConstant import SYNCML_NAMESPACE, NSMAP, MAX_LEN
E = ElementMaker(namespace=SYNCML_NAMESPACE, nsmap=NSMAP)
parser = etree.XMLParser(remove_blank_text=True)
from xml.dom import minidom
from base64 import b16encode, b16decode
class XMLSyncUtilsMixin(SyncCode):
def SyncMLHeader(self, session_id, msg_id, target, source, target_name=None,
source_name=None, dataCred=None, authentication_format='b64',
authentication_type='syncml:auth-basic'):
"""
Since the Header is always almost the same, this is the
way to set one quickly.
"""
xml = (E.SyncHdr(
E.VerDTD('1.2'),
E.VerProto('SyncML/1.2'),
E.SessionID('%s' % session_id),
E.MsgID('%s' % msg_id),
))
target_node = E.Target(E.LocURI(target))
if target_name:
target_node.append(E.LocName(target_name.decode('utf-8')))
xml.append(target_node)
source_node = E.Source(E.LocURI(source))
if source_name:
source_node.append(E.LocName(source_name.decode('utf-8')))
xml.append(source_node)
if dataCred:
xml.append(E.Cred(
E.Meta(E.Format(authentication_format, xmlns='syncml:metinf'),
E.Type(authentication_type, xmlns='syncml:metinf'),),
E.Data(dataCred)
))
return xml
def SyncMLAlert(self, cmd_id, sync_code, target, source, last_anchor,
next_anchor):
"""
Since the Alert section is always almost the same, this is the
way to set one quickly.
"""
xml = (E.Alert(
E.CmdID('%s' % cmd_id),
E.Data('%s' % sync_code),
E.Item(
E.Target(
E.LocURI(target)
),
E.Source(
E.LocURI(source)
),
E.Meta(
E.Anchor(
E.Last(last_anchor),
E.Next(next_anchor)
)
)
)
))
return xml
def SyncMLStatus(self, remote_xml, data_code, cmd_id, next_anchor,
subscription=None):
"""
return a status bloc with all status corresponding to the syncml
commands in remote_xml
"""
namespace = self.getNamespace(remote_xml.nsmap)
#list of element in the SyncBody bloc
sub_syncbody_element_list = remote_xml.xpath('/syncml:SyncML/syncml:SyncBody/*')
message_id = self.getMessageIdFromXml(remote_xml)
status_list = []
target_uri = '%s' %\
remote_xml.xpath('string(/syncml:SyncML/syncml:SyncHdr/syncml:Target/syncml:LocURI)')
source_uri = '%s' %\
remote_xml.xpath('string(/syncml:SyncML/syncml:SyncHdr/syncml:Source/syncml:LocURI)')
if data_code != self.AUTH_REQUIRED:
xml = (E.Status(
E.CmdID('%s' % cmd_id),
E.MsgRef('%s' % message_id),
E.CmdRef('0'),
E.Cmd('SyncHdr'),
E.TargetRef(target_uri),
E.SourceRef(source_uri),
E.Data('%s' % data_code),
))
cmd_id += 1
status_list.append(xml)
for sub_syncbody_element in sub_syncbody_element_list:
if sub_syncbody_element.xpath('local-name()') not in ('Status', 'Final', 'Get'):
xml = (E.Status(
E.CmdID('%s' % cmd_id),
E.MsgRef('%s' % message_id),
E.CmdRef('%s' %\
sub_syncbody_element.xpath('string(.//syncml:CmdID)')),
E.Cmd('%s' % sub_syncbody_element.xpath('name()'))
))
cmd_id += 1
target_ref = sub_syncbody_element.xpath('string(.//syncml:Target/syncml:LocURI)')
if target_ref:
xml.append(E.TargetRef('%s' % target_ref))
source_ref = sub_syncbody_element.xpath('string(.//syncml:Source/syncml:LocURI)')
if source_ref:
xml.append(E.SourceRef('%s' % source_ref))
if sub_syncbody_element.xpath('local-name()') == 'Add':
xml.append(E.Data('%s' % self.ITEM_ADDED))
elif sub_syncbody_element.xpath('local-name()') == 'Alert' and \
sub_syncbody_element.xpath('string(.//syncml:Data)') == \
str(self.SLOW_SYNC):
xml.append(E.Data('%s' % self.REFRESH_REQUIRED))
elif sub_syncbody_element.xpath('local-name()') == 'Alert':
xml.append(E.Item(E.Data(E.Anchor(E.Next(next_anchor)))))
else:
xml.append(E.Data('%s' % self.SUCCESS))
status_list.append(xml)
#FIXME to do a test for Get
if sub_syncbody_element.xpath('local-name()') == 'Get'\
and subscription is not None:
cmd_ref = '%s' % sub_syncbody_element.xpath('string(.//syncml:CmdID)')
syncml_result = self.SyncMLPut(
cmd_id,
subscription,
markup='Results',
cmd_ref=cmd_ref,
message_id=message_id)
if syncml_result is not None:
status_list.append(syncml_result)
cmd_id += 1
return status_list, cmd_id
def SyncMLConfirmation(self, cmd_id=None, target_ref=None, cmd=None,
sync_code=None, msg_ref=None, cmd_ref=None, source_ref=None,
remote_xml=None):
"""
This is used in order to confirm that an object was correctly
synchronized
"""
if remote_xml is not None:
namespace = self.getNamespace(remote_xml.nsmap)
msg_ref = '%s' %\
remote_xml.xpath("string(/syncml:SyncML/syncml:SyncHdr/syncml:MsgID)")
cmd_ref = '%s' % remote_xml.xpath("string(.//syncml:CmdID)")
target_ref = '%s' % remote_xml.xpath("string(.//syncml:Target/syncml:LocURI)")
source_ref = '%s' % remote_xml.xpath("string(.//syncml:Source/syncml:LocURI)")
xml = E.Status()
if cmd_id:
xml.append(E.CmdID('%s' % cmd_id))
if msg_ref:
xml.append(E.MsgRef(msg_ref))
if cmd_ref:
xml.append(E.CmdRef(cmd_ref))
if cmd:
xml.append(E.Cmd(cmd))
if target_ref:
xml.append(E.TargetRef(target_ref))
if source_ref:
xml.append(E.SourceRef(source_ref))
if sync_code:
xml.append(E.Data('%s'% sync_code))
return xml
from base64 import b64encode, b64decode
from imp import load_source
def encode(format, string_to_encode):
"""
return the string_to_encode encoded with format format
"""
if not format:
return string_to_encode
if format == 'b64':
return b64encode(string_to_encode)
#elif format is .... put here the other formats
else:#if there is no format corresponding with format, raise an error
LOG('encode : unknown or not implemented format : ', INFO, format)
raise ValueError, "Sorry, the server ask for the format %s but \
it's unknow or not implemented" % format
def decode(format, string_to_decode):
"""
return the string_to_decode decoded with format format
"""
string_to_decode = string_to_decode.encode('utf-8')
if not format:
return string_to_decode
if format == 'b64':
return b64decode(string_to_decode)
#elif format is .... put here the other formats
else:#if there is no format corresponding with format, raise an error
LOG('decode : unknown or not implemented format :', INFO, format)
raise ValueError, "Sorry, the format %s is unknow or \
not implemented" % format
def isDecodeEncodeTheSame(string_encoded, string_decoded, format):
"""
return True if the string_encoded is equal to string_decoded encoded
in format
"""
return encode(format, string_decoded) == string_encoded
def xml2wbxml(xml):
"""
convert xml string to wbxml using a temporary file
"""
#LOG('xml2wbxml starting ...', DEBUG, '')
import os
f = open('/tmp/xml2wbxml', 'w')
f.write(xml)
f.close()
os.system('/usr/bin/xml2wbxml -o /tmp/xml2wbxml /tmp/xml2wbxml')
f = open('/tmp/xml2wbxml', 'r')
wbxml = f.read()
f.close()
return wbxml
def wbxml2xml(wbxml):
"""
convert wbxml string to xml using a temporary file
"""
#LOG('wbxml2xml starting ...', DEBUG, '')
import os
f = open('/tmp/wbxml2xml', 'w')
f.write(wbxml)
f.close()
os.system('/usr/bin/wbxml2xml -o /tmp/wbxml2xml /tmp/wbxml2xml')
f = open('/tmp/wbxml2xml', 'r')
xml = f.read()
f.close()
return xml
def getConduitByName(conduit_name):
"""
Get Conduit Object by given name.
The Conduit can be located in Any Products according to naming Convention
Products.<Product Name>.Conduit.<Conduit Module> ,if conduit_name equal module's name.
By default Conduit must be defined in Products.ERP5SyncML.Conduit.<Conduit Module>
Conduit can also be defined as Extension to have it editable through the web, in this
case its definition must be Extensions.<Conduit Module>
"""
if conduit_name.startswith('Products'):
path = conduit_name
conduit_name = conduit_name.split('.')[-1]
conduit_module = __import__(path, globals(), locals(), [''])
elif conduit_name.startswith('Extensions'):
path = "%s/%s.py" %(getConfiguration().instancehome, conduit_name.replace('.', '/'))
conduit_name = conduit_name.split('.')[-1]
conduit_module = load_source(conduit_name, path, file(path))
else:
from Products.ERP5SyncML import Conduit
conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]),
globals(), locals(), [''])
conduit_instance = getattr(conduit_module, conduit_name)()
return conduit_instance
def resolveSyncmlStatusCode(context, category_id):
"""Return reference of syncml_status_code category
"""
category_tool = getToolByName(context.getPortalObject(), 'portal_categories')
return category_tool.getCategoryValue(category_id,
base_category='syncml_status_code')\
.getReference()
def resolveSyncmlAlertCode(portal, category_id):
"""Return reference of syncml_alert_code category
"""
category_tool = getToolByName(portal, 'portal_categories')
return category_tool.getCategoryValue(category_id,
base_category='syncml_alert_code')\
.getReference()
def getAlertCodeFromXML(xml):
"""
Return the value of the alert code inside the full syncml message
"""
alert_code = '%s' % xml.xpath('string(/syncml:SyncML/syncml:SyncBody/'\
'syncml:Alert/syncml:Data)',
namespaces=xml.nsmap)
return alert_code
def checkAlert(xml):
"""
Check if there's an Alert section in the xml_stream
"""
return bool(xml.xpath('string(/syncml:SyncML/syncml:SyncBody/syncml:Alert)',
namespaces=xml.nsmap))
def getMessageIdFromXml(xml):
"""
We will retrieve the message id of the message
"""
return int(xml.xpath('string(/syncml:SyncML/syncml:SyncHdr/syncml:MsgID)',
namespaces=xml.nsmap))
def getSubscriptionUrlFromXML(xml):
"""return the source URI of the syncml header
"""
return '%s' % xml.xpath('string(//syncml:SyncHdr/syncml:Source/'\
'syncml:LocURI)', namespaces=xml.nsmap)
def checkFinal(xml):
"""
Check if there's an Final section in the xml_stream
The end sections (inizialisation, modification) have this tag
"""
return bool(xml.xpath('/syncml:SyncML/syncml:SyncBody/syncml:Final',
namespaces=xml.nsmap))
def setRidWithMap(xml, subscriber):
"""
get all the local objects of the given target id and set them the rid with
the given source id (in the Map section)
"""
item_list = xml.xpath('/syncml:SyncML/syncml:SyncBody/syncml:Map/syncml:MapItem',
namespaces=xml.nsmap)
for map_item in item_list:
gid = '%s' % map_item.xpath('string(.//syncml:Target/syncml:LocURI)', namespaces=xml.nsmap)
signature = subscriber.getSignatureFromGid(gid)
rid = '%s' % map_item.xpath('string(.//syncml:Source/syncml:LocURI)', namespaces=xml.nsmap)
signature.setRid(rid)
def getSyncBodyStatusList(xml):
"""
return the list of dictionary corredponding to the data of each status bloc
the data are : cmd, code and source
"""
status_list = []
status_node_list = xml.xpath('//syncml:Status', namespaces=xml.nsmap)
for status in status_node_list:
tmp_dict = {}
tmp_dict['cmd'] = '%s' % status.xpath('string(./syncml:Cmd)',
namespaces=xml.nsmap)
tmp_dict['code'] = '%s' % status.xpath('string(./syncml:Data)',
namespaces=xml.nsmap)
tmp_dict['source'] = '%s' % status.xpath('string(./syncml:SourceRef)',
namespaces=xml.nsmap)
tmp_dict['target'] = '%s' % status.xpath('string(./syncml:TargetRef)',
namespaces=xml.nsmap)
status_list.append(tmp_dict)
return status_list
def getDataText(xml):
"""
return the section data in text form, it's usefull for the VCardConduit
"""
return '%s' % xml.xpath('string(.//syncml:Item/syncml:Data)',
namespaces=xml.nsmap)
def SyncMLChal(self, cmd_id, cmd, target_ref, source_ref, auth_format,
auth_type, auth_code):
"""
This is used in order to ask crendentials
"""
xml = (E.Status(
E.CmdID('%s' % cmd_id),
E.MsgRef('1'),
E.CmdRef('0'),
E.Cmd(cmd),
E.TargetRef(target_ref),
E.SourceRef(source_ref),
E.Chal(
E.Meta(
E.Format(auth_format, xmlns='syncml:metinf'),
E.Type(auth_type, xmlns='syncml:metinf')
)
),
E.Data('%s' % auth_code)
))
return xml
def SyncMLPut(self, cmd_id, subscription, markup='Put', cmd_ref=None,
message_id=None):
"""
this is used to inform the server of the CTType version supported
but if the server use it to respond to a Get request, it's a <Result> markup
instead of <Put>
"""
conduit_name = subscription.getConduit()
conduit = self.getConduitByName(conduit_name)
xml = None
#if the conduit support the SyncMLPut :
if getattr(conduit, 'getCapabilitiesCTTypeList', None) is not None and \
getattr(conduit, 'getCapabilitiesVerCTList', None) is not None and \
getattr(conduit, 'getPreferedCapabilitieVerCT', None) is not None:
xml = Element('{%s}%s' % (SYNCML_NAMESPACE, markup))
xml.append(E.CmdID('%s' % cmd_id))
if message_id:
xml.append(E.MsgRef('%s' % message_id))
if cmd_ref:
xml.append(E.CmdRef('%s' % cmd_ref))
xml.extend((E.Meta(E.Type('application/vnd.syncml-devinf+xml')),
E.Item(E.Source(E.LocURI('./devinf11')),
E.Data(E.DevInf(
E.VerDTD('1.1'),
E.Man('Nexedi'),
E.Mod('ERP5SyncML'),
E.OEM('Open Source'),
E.SwV('0.1'),
E.DevID(subscription.getSubscriptionUrl()),
E.DevTyp('workstation'),
E.UTC(),
E.DataStore(E.SourceRef(subscription.getSourceURI()))
)
)
)))
data_store = xml.find('{%(ns)s}Item/{%(ns)s}Data/{%(ns)s}DevInf/{%(ns)s}DataStore' % {'ns': SYNCML_NAMESPACE})
tx_element_list = []
rx_element_list = []
for type in conduit.getCapabilitiesCTTypeList():
if type != self.MEDIA_TYPE['TEXT_XML']:
for x_version in conduit.getCapabilitiesVerCTList(type):
rx_element_list.append(E.Rx(E.CTType(type), E.VerCT(x_version)))
tx_element_list.append(E.Tx(E.CTType(type), E.VerCT(x_version)))
rx_pref = Element('{%s}Rx-Pref' % SYNCML_NAMESPACE)
rx_pref.extend((E.CTType(conduit.getPreferedCapabilitieCTType()),
E.VerCT(conduit.getPreferedCapabilitieVerCT())))
data_store.append(rx_pref)
data_store.extend(rx_element_list)
tx_pref = Element('{%s}Tx-Pref' % SYNCML_NAMESPACE)
tx_pref.extend((E.CTType(conduit.getPreferedCapabilitieCTType()),
E.VerCT(conduit.getPreferedCapabilitieVerCT())))
data_store.append(tx_pref)
data_store.extend(tx_element_list)
data_store.append(E.SyncCap(
E.SyncType('2'),
E.SyncType('1'),
E.SyncType('4'),
E.SyncType('6')
))
return xml
def getDataSubNode(xml):
"""
Return the content of syncml stream
"""
object_node_list = xml.xpath('.//syncml:Item/syncml:Data/*[1]',
namespaces=xml.nsmap)
if object_node_list:
return object_node_list[0]
return None
def getXupdateObject(object_xml=None, old_xml=None):
"""
Generate the xupdate with the new object and the old xml
"""
erp5diff = ERP5Diff()
erp5diff.compare(old_xml, object_xml)
#Upper version of ERP5Diff produce valid XML.
xupdate = erp5diff.outputString()
#omit xml declaration
xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
return xupdate
#def cutXML(xml_string):
#"""
#Sliced a xml tree a return two fragment
#"""
#line_list = xml_string.split('\n')
#short_string = '\n'.join(line_list[:MAX_LINES])
#rest_string = '\n'.join(line_list[MAX_LINES:])
#xml_string = etree.CDATA(short_string.decode('utf-8'))
#return xml_string, rest_string
def cutXML(xml_string):
"""
Sliced a xml tree a return two fragment
"""
short_string = xml_string[:MAX_LEN]
rest_string = xml_string[MAX_LEN:]
xml_string = etree.CDATA(short_string.decode('utf-8'))
return xml_string, rest_string
class XMLSyncUtilsMixin(object):
def sendMail(self, fromaddr, toaddr, id_sync, msg):
"""
......@@ -303,163 +286,20 @@ class XMLSyncUtilsMixin(SyncCode):
#server.sendmail(fromaddr, "seb@localhost", msg)
server.quit()
def getNamespace(self, nsmap):
"""
Set the namespace prefix, check if argument is conform
and return the full namespace updated for syncml
nsmap -- The namespace of the received xml
"""
#search urn compatible in the namespaces of nsmap
urns = filter(lambda v: v.upper() in self.URN_LIST, nsmap.values())
if urns:
namespace = etree.FunctionNamespace(urns[0])
namespace.prefix = 'syncml'
return namespace
else:
raise ValueError, "Sorry, the given namespace is not supported"
def addXMLObject(self, **kw):
"""
Add an object with the SyncML protocol
"""
return self._createAddOrReplaceNode('Add', **kw)
def deleteXMLObject(self, cmd_id=0, object_gid=None, rid=None):
"""
Delete an object with the SyncML protocol
"""
if rid:
elem_to_append = E.Target(E.LocURI('%s' % rid))
else:
elem_to_append = E.Source(E.LocURI('%s' % object_gid))
xml = (E.Delete(
E.CmdID('%s' % cmd_id),
E.Item(
elem_to_append
)
))
return etree.tostring(xml, encoding='utf-8', pretty_print=True)
def replaceXMLObject(self, **kw):
"""
Replace an object with the SyncML protocol
"""
return self._createAddOrReplaceNode('Replace', **kw)
def _createAddOrReplaceNode(self, id_tag, cmd_id=0, object=None,
xml_string=None, more_data=False, gid=None,
rid=None, media_type=None):
"""Mixin for addXMLObject() and replaceXMLObject()
"""
data_node = E.Data()
if media_type == self.MEDIA_TYPE['TEXT_XML']:
if isinstance(xml_string, str):
data_node.append(etree.XML(xml_string, parser=parser))
elif isinstance(xml_string, etree.CDATA):
#xml_string could be Data element if partial XML
data_node.text = xml_string
else:
data_node.append(xml_string)
else:
if isinstance(xml_string, etree.CDATA):
data_node.text = xml_string
else:
cdata = etree.CDATA(xml_string.decode('utf-8'))
data_node.text = cdata
main_tag = Element('{%s}%s' % (SYNCML_NAMESPACE, id_tag))
main_tag.append(E.CmdID('%s' % cmd_id))
main_tag.append(E.Meta(E.Type(media_type)))
main_tag.append(E.Item(E.Source(E.LocURI(gid)), data_node))
if more_data:
item_node = main_tag.find('{%s}Item' % SYNCML_NAMESPACE)
item_node.append(E.MoreData())
return etree.tostring(main_tag, encoding='utf-8', pretty_print=True)
def getXupdateObject(self, object_xml=None, old_xml=None):
"""
Generate the xupdate with the new object and the old xml
"""
erp5diff = ERP5Diff()
erp5diff.compare(old_xml, object_xml)
if isinstance(erp5diff._result, minidom.Document):
#XXX While ERP5Diff use minidom, this part needs to be keeped.
#minidom is buggy, add namespace declaration, and version attributes
attr_version = erp5diff._result.createAttributeNS(None, 'version')
attr_version.value = '1.0'
erp5diff._result.documentElement.setAttributeNodeNS(attr_version)
attr_ns = erp5diff._result.createAttributeNS(None, 'xmlns:xupdate')
attr_ns.value = 'http://www.xmldb.org/xupdate'
erp5diff._result.documentElement.setAttributeNodeNS(attr_ns)
xupdate = erp5diff._result.toxml('utf-8')
else:
#Upper version of ERP5Diff produce valid XML.
xupdate = erp5diff.outputString()
#omit xml declaration
xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
return xupdate
def getSessionIdFromXml(self, xml):
"""
We will retrieve the session id of the message
"""
return int(xml.xpath('string(/syncml:SyncML/syncml:SyncHdr/syncml:SessionID)',
namespaces=xml.nsmap))
def getMessageIdFromXml(self, xml):
"""
We will retrieve the message id of the message
"""
return int(xml.xpath('string(/syncml:SyncML/syncml:SyncHdr/syncml:MsgID)',
namespaces=xml.nsmap))
def getTarget(self, xml):
"""
return the target in the SyncHdr section
"""
return '%s' %\
xml.xpath('string(/syncml:SyncML/syncml:SyncHdr/syncml:Target/syncml:LocURI)',
namespaces=xml.nsmap)
def getAlertLastAnchor(self, xml):
"""
Return the value of the last anchor, in the
alert section of the xml_stream
"""
return '%s' %\
xml.xpath('string(.//syncml:Alert/syncml:Item/syncml:Meta/syncml:Anchor/syncml:Last)',
namespaces=xml.nsmap)
def getAlertNextAnchor(self, xml):
"""
Return the value of the next anchor, in the
alert section of the xml_stream
"""
return '%s' %\
xml.xpath('string(.//syncml:Alert/syncml:Item/syncml:Meta/syncml:Anchor/syncml:Next)',
namespaces=xml.nsmap)
def getSourceURI(self, xml):
"""
return the source uri of the data base
"""
return '%s' %\
xml.xpath('string(//syncml:SyncBody/syncml:Alert/syncml:Item/syncml:Source/syncml:LocURI)',
namespaces=xml.nsmap)
def getTargetURI(self, xml):
"""
return the target uri of the data base
"""
return '%s' %\
xml.xpath('string(//syncml:SyncBody/syncml:Alert/syncml:Item/syncml:Target/syncml:LocURI)',
namespaces=xml.nsmap)
def getSubscriptionUrlFromXML(self, xml):
"""
return the source URI of the syncml header
"""
return '%s' % xml.xpath('string(//syncml:SyncHdr/syncml:Source/syncml:LocURI)',
namespaces=xml.nsmap)
#def getNamespace(self, nsmap):
#"""
#Set the namespace prefix, check if argument is conform
#and return the full namespace updated for syncml
#nsmap -- The namespace of the received xml
#"""
##search urn compatible in the namespaces of nsmap
#urns = filter(lambda v: v.upper() in self.URN_LIST, nsmap.values())
#if urns:
#namespace = etree.FunctionNamespace(urns[0])
#namespace.prefix = 'syncml'
#return namespace
#else:
#raise ValueError, "Sorry, the given namespace is not supported"
def getStatusTarget(self, xml):
"""
......@@ -483,96 +323,7 @@ class XMLSyncUtilsMixin(SyncCode):
cmd = None
if xml.xpath('local-name()') == 'Status':
cmd = '%s' % xml.xpath('string(syncml:Cmd)', namespaces=xml.nsmap)
return cmd
def getCred(self, xml):
"""
return the credential information : type, format and data
"""
format = '%s' %\
xml.xpath('string(/syncml:SyncML/syncml:SyncHdr/syncml:Cred/syncml:Meta/*[local-name() = "Format"])',
namespaces=xml.nsmap)
type = '%s' %\
xml.xpath('string(/syncml:SyncML/syncml:SyncHdr/syncml:Cred/syncml:Meta/*[local-name() = "Type"])',
namespaces=xml.nsmap)
data = '%s' % xml.xpath('string(/syncml:SyncML/syncml:SyncHdr/syncml:Cred/syncml:Data)',
namespaces=xml.nsmap)
return (format, type, data)
def checkCred(self, xml):
"""
Check if there's a Cred section in the xml_stream
"""
return bool(xml.xpath('string(/syncml:SyncML/syncml:SyncHdr/syncml:Cred)', namespaces=xml.nsmap))
def getChal(self, xml):
"""
return the chalenge information : format and type
"""
format = '%s' % xml.xpath('string(//*[local-name() = "Format"])', namespaces=xml.nsmap)
type = '%s' % xml.xpath('string(//*[local-name() = "Type"])', namespaces=xml.nsmap)
return (format, type)
def checkChal(self, xml):
"""
Check if there's a Chal section in the xml_stream
"""
return bool(xml.xpath('string(/syncml:SyncML/syncml:SyncBody/syncml:Status/syncml:Chal)',
namespaces=xml.nsmap))
def checkMap(self, xml):
"""
Check if there's a Map section in the xml_stream
"""
return bool(xml.xpath('string(/syncml:SyncML/syncml:SyncBody/syncml:Map)', namespaces=xml.nsmap))
def setRidWithMap(self, xml, subscriber):
"""
get all the local objects of the given target id and set them the rid with
the given source id (in the Map section)
"""
item_list = xml.xpath('/syncml:SyncML/syncml:SyncBody/syncml:Map/syncml:MapItem',
namespaces=xml.nsmap)
for map_item in item_list:
gid = '%s' % map_item.xpath('string(.//syncml:Target/syncml:LocURI)', namespaces=xml.nsmap)
signature = subscriber.getSignatureFromGid(gid)
rid = '%s' % map_item.xpath('string(.//syncml:Source/syncml:LocURI)', namespaces=xml.nsmap)
signature.setRid(rid)
def getAlertCodeFromXML(self, xml):
"""
Return the value of the alert code inside the full syncml message
"""
alert_code = '%s' %\
xml.xpath('string(/syncml:SyncML/syncml:SyncBody/syncml:Alert/syncml:Data)',
namespaces=xml.nsmap)
if alert_code:
return int(alert_code)
else:
return None
def checkAlert(self, xml):
"""
Check if there's an Alert section in the xml_stream
"""
return bool(xml.xpath('string(/syncml:SyncML/syncml:SyncBody/syncml:Alert)',
namespaces=xml.nsmap))
def checkSync(self, xml):
"""
Check if there's an Sync section in the xml_stream
"""
return bool(xml.xpath('string(/syncml:SyncML/syncml:SyncBody/syncml:Sync)',
namespaces=xml.nsmap))
def checkFinal(self, xml):
"""
Check if there's an Final section in the xml_stream
The end sections (inizialisation, modification) have this tag
"""
return bool(xml.xpath('/syncml:SyncML/syncml:SyncBody/syncml:Final',
namespaces=xml.nsmap))
return cmd
def checkStatus(self, xml):
"""
......@@ -581,68 +332,6 @@ class XMLSyncUtilsMixin(SyncCode):
return bool(xml.xpath('string(/syncml:SyncML/syncml:SyncBody/syncml:Status)',
namespaces=xml.nsmap))
def getSyncActionList(self, xml):
"""
return the list of the action (could be "add", "replace", "delete").
"""
return xml.xpath('//syncml:Add|//syncml:Delete|//syncml:Replace',
namespaces=xml.nsmap)
def getSyncBodyStatusList(self, xml):
"""
return the list of dictionary corredponding to the data of each status bloc
the data are : cmd, code and source
"""
status_list = []
status_node_list = xml.xpath('//syncml:Status', namespaces=xml.nsmap)
for status in status_node_list:
tmp_dict = {}
tmp_dict['cmd'] = '%s' % status.xpath('string(./syncml:Cmd)',
namespaces=xml.nsmap)
tmp_dict['code'] = '%s' % status.xpath('string(./syncml:Data)',
namespaces=xml.nsmap)
tmp_dict['source'] = '%s' % status.xpath('string(./syncml:SourceRef)',
namespaces=xml.nsmap)
tmp_dict['target'] = '%s' % status.xpath('string(./syncml:TargetRef)',
namespaces=xml.nsmap)
status_list.append(tmp_dict)
return status_list
def getDataText(self, xml):
"""
return the section data in text form, it's usefull for the VCardConduit
"""
return '%s' % xml.xpath('string(.//syncml:Item/syncml:Data)',
namespaces=xml.nsmap)
def getDataSubNode(self, xml):
"""
Return the node starting with <object....> of the action
"""
object_node_list = xml.xpath('.//syncml:Item/syncml:Data/*[1]',
namespaces=xml.nsmap)
if object_node_list:
return object_node_list[0]
return None
def getActionId(self, xml):
"""
Return the rid of the object described by the action
"""
id = '%s' % xml.xpath('string(.//syncml:Item/syncml:Source/syncml:LocURI)',
namespaces=xml.nsmap)
if not id:
id = '%s' % xml.xpath('string(.//syncml:Item/syncml:Target/syncml:LocURI)',
namespaces=xml.nsmap)
return id
def checkActionMoreData(self, xml):
"""
Return the rid of the object described by the action
"""
return bool(xml.xpath('.//syncml:Item/syncml:MoreData',
namespaces=xml.nsmap))
def getActionType(self, xml):
"""
Return the type of the object described by the action
......@@ -650,529 +339,6 @@ class XMLSyncUtilsMixin(SyncCode):
return '%s' % xml.xpath('string(.//syncml:Meta/syncml:Type)',
namespaces=xml.nsmap)
def cutXML(self, xml_string):
"""
Sliced a xml tree a return two fragment
"""
line_list = xml_string.split('\n')
short_string = '\n'.join(line_list[:self.MAX_LINES])
rest_string = '\n'.join(line_list[self.MAX_LINES:])
xml_string = etree.CDATA(short_string.decode('utf-8'))
return xml_string, rest_string
def getSyncMLData(self, domain=None, remote_xml=None, cmd_id=0,
subscriber=None, xml_confirmation_list=None, conduit=None,
max=None, **kw):
"""
This generate the syncml data message. This returns a string
with all modification made locally (ie replace, add ,delete...)
if object is not None, this usually means we want to set the
actual xupdate on the signature.
"""
#LOG('getSyncMLData starting...', DEBUG, domain.getId())
if isinstance(conduit, str):
conduit = self.getConduitByName(conduit)
if xml_confirmation_list is None:
xml_confirmation_list = []
local_gid_list = []
syncml_data_list = kw.get('syncml_data_list', [])
result = {'finished': True}
if isinstance(remote_xml, (str, unicode)):
remote_xml = etree.XML(remote_xml, parser=parser)
if domain.isOneWayFromServer():
#Do not set object_path_list, subscriber send nothing it's a client
subscriber.setRemainingObjectPathList([])
elif subscriber.getRemainingObjectPathList() is None:
object_list = domain.getObjectList()
object_path_list = [x.getPhysicalPath() for x in object_list]
subscriber.setRemainingObjectPathList(object_path_list)
if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_VCARD']:
#here the method getGidFromObject don't return the good gid because
#the conduit use the catalog to determine it and object are not yet
#cataloged so if there is many times the same gid, we must count it
gid_not_encoded_list = []
for object in object_list:
#LOG('getSyncMLData :', DEBUG, 'object:%s, objectTitle:%s, local_gid_list:%s' % (object, object.getTitle(), local_gid_list))
gid = b16decode(domain.getGidFromObject(object))
if gid in gid_not_encoded_list:
number = len([item for item in gid_not_encoded_list if item.startswith(gid)])
if number:
gid = '%s__%s' % (gid, str(number+1))
gid_not_encoded_list.append(gid)
local_gid_list.append(b16encode(gid))
#LOG('getSyncMLData :', DEBUG,'gid_not_encoded_list:%s, local_gid_list:%s, gid:%s' % (gid_not_encoded_list, local_gid_list, gid))
else:
local_gid_list = [domain.getGidFromObject(x) for x in object_list]
# Objects to remove
#LOG('getSyncMLData remove object to remove ...', DEBUG, '')
for object_gid in subscriber.getGidList():
if object_gid not in local_gid_list:
# This is an object to remove
signature = subscriber.getSignatureFromGid(object_gid)
if signature.getStatus() != self.PARTIAL:
# If partial, then we have a signature but no local object
rid = signature.getRid()
syncml_data_list.append(self.deleteXMLObject(object_gid=object_gid,
rid=rid,
cmd_id=cmd_id))
cmd_id += 1
# Delete Signature if object does not exist anymore
subscriber.delSignature(object_gid)
local_gid_list = []
loop = 0
for object_path in subscriber.getRemainingObjectPathList():
if max is not None and loop >= max:
result['finished'] = False
break
#LOG('getSyncMLData object_path', INFO, object_path)
object = self.getPortalObject().unrestrictedTraverse(object_path)
status = self.SENT
object_gid = domain.getGidFromObject(object)
if not object_gid:
continue
local_gid_list += [object_gid]
force = False
if ''.join(syncml_data_list).count('\n') < self.MAX_LINES and not \
object.getId().startswith('.'):
# If not we have to cut
#LOG('getSyncMLData', 0, 'object_path: %s' % '/'.join(object_path))
#LOG('getSyncMLData', 0, 'xml_mapping: %s' % str(domain.getXMLMapping()))
#LOG('getSyncMLData', 0, 'code: %s' % str(self.getAlertCodeFromXML(remote_xml)))
#LOG('getSyncMLData', 0, 'gid_list: %s' % str(local_gid_list))
#LOG('getSyncMLData', 0, 'subscriber.getGidList: %s' % subscriber.getGidList())
#LOG('getSyncMLData', 0, 'hasSignature: %s' % str(subscriber.hasSignature(object_gid)))
#LOG('getSyncMLData', 0, 'alert_code == slowsync: %s' % str(self.getAlertCodeFromXML(remote_xml) == self.SLOW_SYNC))
signature = subscriber.getSignatureFromGid(object_gid)
## Here we first check if the object was modified or not by looking at dates
status = self.SENT
more_data = False
# For the case it was never synchronized, we have to send everything
if signature is not None and signature.getXMLMapping() is None:
pass
elif signature is None or\
(not signature.hasXML() and\
signature.getStatus() != self.PARTIAL) or\
self.getAlertCodeFromXML(remote_xml) == self.SLOW_SYNC:
#LOG('getSyncMLData', DEBUG, 'Current object.getPath: %s' % object.getPath())
xml_string = conduit.getXMLFromObjectWithId(object,
xml_mapping=domain.getXMLMapping())
gid = subscriber.getGidFromObject(object)
signature = Signature(id=gid, object=object).__of__(subscriber)
signature.setTempXML(xml_string)
if xml_string and xml_string.count('\n') > self.MAX_LINES:
more_data = True
xml_string, rest_string = self.cutXML(xml_string)
signature.setPartialXML(rest_string)
status = self.PARTIAL
signature.setAction('Add')
#in first, we try with rid if there is one
gid = signature.getRid() or signature.getGid()
# XML method might returns None value
if xml_string:
syncml_data_list.append(self.addXMLObject(
cmd_id=cmd_id,
object=object,
gid=gid,
xml_string=xml_string,
more_data=more_data,
media_type=subscriber.getMediaType()))
cmd_id += 1
signature.setStatus(status)
subscriber.addSignature(signature)
elif signature.getStatus() in (self.NOT_SYNCHRONIZED,
self.PUB_CONFLICT_MERGE,):
# We don't have synchronized this object yet but it has a signature
xml_object = conduit.getXMLFromObjectWithId(object,
xml_mapping=domain.getXMLMapping())
#LOG('getSyncMLData', DEBUG, 'checkMD5: %s' % str(signature.checkMD5(xml_object)))
#LOG('getSyncMLData', DEBUG, 'getStatus: %s' % str(signature.getStatus()))
if signature.getStatus() == self.PUB_CONFLICT_MERGE:
xml_confirmation_list.append(self.SyncMLConfirmation(
cmd_id=cmd_id,
source_ref=signature.getGid(),
sync_code=self.CONFLICT_MERGE,
cmd='Replace'))
set_synchronized = True
if not signature.checkMD5(xml_object):
set_synchronized = False
if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
# If there is no xml, we re-send all the objects
xml_string = xml_object
else:
# This object has changed on this side, we have to generate some xmldiff
gid = signature.getGid()
xml_object_with_gid = conduit.replaceIdFromXML(xml_object, 'gid',
gid)
previous_xml_with_gid = conduit.replaceIdFromXML(
signature.getXML(), 'gid', gid)
xml_string = self.getXupdateObject(xml_object_with_gid,
previous_xml_with_gid)
#LOG('XMLSyncUtils diff:%s' % object.getPath(), INFO,
#xupdate_string)
if xml_string.count('\n') > self.MAX_LINES:
# This make comment fails, so we need to replace
more_data = True
xml_string, rest_string = self.cutXML(xml_string)
signature.setPartialXML(rest_string)
status = self.PARTIAL
signature.setAction('Replace')
signature.setStatus(status)
rid = signature.getRid()#in first, we try with rid if there is
gid = signature.getGid()
syncml_data_list.append(self.replaceXMLObject(
cmd_id=cmd_id, object=object,
gid=gid, rid=rid,
xml_string=xml_string,
more_data=more_data,
media_type=subscriber.getMediaType()))
cmd_id += 1
signature.setTempXML(xml_object)
# Now we can apply the xupdate from the subscriber
subscriber_xupdate = signature.getSubscriberXupdate()
#LOG('getSyncMLData subscriber_xupdate', DEBUG, subscriber_xupdate)
if subscriber_xupdate is not None:
# The modification in the xml from signature is compare and update
# with xml_xupdate from subscriber
previous_xml_with_gid = conduit.replaceIdFromXML(
signature.getXML(), 'gid', gid, as_string=False)
conduit.updateNode(xml=subscriber_xupdate, object=object,
previous_xml=previous_xml_with_gid,
force=(domain.getDomainType() == self.SUB),
simulate=False)
xml_object = conduit.getXMLFromObjectWithId(object,
xml_mapping=domain.getXMLMapping())
signature.setTempXML(xml_object)
if set_synchronized: # We have to do that after this previous update
# We should not have this case when we are in CONFLICT_MERGE
signature.setStatus(self.SYNCHRONIZED)
elif signature.getStatus() == self.PUB_CONFLICT_CLIENT_WIN:
# We have decided to apply the update
# XXX previous_xml will be geXML instead of getTempXML because
# some modification was already made and the update
# may not apply correctly
xml_update = signature.getPartialXML()
previous_xml_with_gid = conduit.replaceIdFromXML(signature.getXML(),
'gid', gid,
as_string=False)
conduit.updateNode(xml=xml_update, object=object,
previous_xml=previous_xml_with_gid, force=True,
gid=gid)
xml_confirmation_list.append(self.SyncMLConfirmation(
cmd_id=cmd_id,
target_ref=object_gid,
sync_code=self.CONFLICT_CLIENT_WIN,
cmd='Replace'))
signature.setStatus(self.SYNCHRONIZED)
elif signature.getStatus() == self.PARTIAL:
# Receive the chunk of partial xml
if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
xml_string = conduit.getXMLFromObjectWithId(object,
xml_mapping=domain.getXMLMapping())
else:
# Wrapp it into CDATA
xml_string = signature.getPartialXML(default='')
if xml_string.count('\n') > self.MAX_LINES:
more_data = True
status = self.PARTIAL
xml_string = signature.getFirstChunkPdata(self.MAX_LINES)
xml_string = etree.CDATA(xml_string)
signature.setStatus(status)
if signature.getAction() == 'Replace':
rid = signature.getRid()
# In first, we try with rid if there is one
gid = signature.getGid()
syncml_data_list.append(self.replaceXMLObject(
cmd_id=cmd_id,
object=object,
gid=gid,
rid=rid,
xml_string=xml_string,
more_data=more_data,
media_type=subscriber.getMediaType()))
elif signature.getAction() == 'Add':
#in fisrt, we try with rid if there is one
gid = signature.getRid() or signature.getGid()
syncml_data_list.append(self.addXMLObject(
cmd_id=cmd_id,
object=object,
gid=gid,
xml_string=xml_string,
more_data=more_data,
media_type=subscriber.getMediaType()))
if not more_data:
subscriber.removeRemainingObjectPath(object_path)
else:
result['finished'] = 1
break
loop += 1
result['syncml_data_list'] = syncml_data_list
result['xml_confirmation_list'] = xml_confirmation_list
result['cmd_id'] = cmd_id
return result
def applyActionList(self, domain=None, subscriber=None, cmd_id=0,
remote_xml=None, conduit=None, simulate=False):
"""
This just look to a list of action to do, then id applies
each action one by one, thanks to a conduit
"""
namespace = self.getNamespace(remote_xml.nsmap)
xml_confirmation_list = []
has_next_action = False
gid_from_xml_list = []
destination = self.unrestrictedTraverse(domain.getDestinationPath())
#LOG('applyActionList args', DEBUG, 'domain : %s\n subscriber : %s\n cmd_id: %s'\
#% (domain.getPath(), subscriber.getPath(), cmd_id))
#LOG('XMLSyncUtils applyActionList', DEBUG, self.getSyncActionList(remote_xml))
for action in self.getSyncActionList(remote_xml):
conflict_list = []
status_code = self.SUCCESS
# Thirst we have to check the kind of action it is
# The rid is the Temporary GUID (SYNCML Protocol). the rid sent by the
# client unlike gid. The rid is in MapItem for each Action Map it's the LocURI in
# the action.
gid = rid = self.getActionId(action)
#The action delete hasn't need a gid and retrieve the gid of conduit for
#object.
if action.xpath('local-name()') != 'Delete':
data_action = self.getDataSubNode(action)
if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
#data in unicode
data_action = self.getDataText(action)
if getattr(conduit, 'getGidFromXML', None) is not None and \
conduit.getGidFromXML(data_action, namespace, gid_from_xml_list):
gid = conduit.getGidFromXML(data_action, namespace, gid_from_xml_list)
gid_from_xml_list.append(gid)
gid = b16encode(gid)
#the rid unlike gid, it's the rid or gid (if rid == gid) will use for
#retrieve object and send response to client
signature = subscriber.getSignatureFromGid(gid)
object = subscriber.getObjectFromGid(gid)
object_id = domain.generateNewIdWithGenerator(object=destination, gid=gid)
if rid == gid:
#We can't use our own gid and the temporary GUID is useless
rid = None
if signature is not None:
signature.setRid(rid)
else:
signature = Signature(id=gid, rid=rid, status=self.NOT_SYNCHRONIZED,
object=object).__of__(subscriber)
signature.setObjectId(object_id)
subscriber.addSignature(signature)
force = signature.getForce()
data_node = action.find('.//{%(ns)s}Item/{%(ns)s}Data'\
% {'ns': SYNCML_NAMESPACE})
if data_node is not None:
if len(data_node):
data = etree.tostring(data_node[0])
else:
data = data_node.text or ''
else:
data = ''
if not self.checkActionMoreData(action):
# This is the last chunk of a partial xml
# or this is just an entire data chunk
data_subnode = None
if signature.hasPartialXML():
# rebuild the entire data
signature.appendPartialXML(data)
# fetch data as string
data_subnode = signature.getPartialXML()
# clear partial data cache on Signature
signature.setPartialXML(None)
#LOG('applyActionList', DEBUG, 'data_subnode: %s' % data_subnode)
if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']:
data_subnode = etree.XML(data_subnode, parser=parser)
else:
if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
data_subnode = self.getDataText(action)
else:
data_subnode = self.getDataSubNode(action)
if action.xpath('local-name()') == 'Add':
# Then store the xml of this new subobject
reset = False
if object is None:
add_data = conduit.addNode(xml=data_subnode,
object=destination,
object_id=object_id,
domain=domain)
conflict_list.extend(add_data['conflict_list'])
# Retrieve directly the object from addNode
object = add_data['object']
if object is not None:
signature.setPath(object.getPhysicalPath())
signature.setObjectId(object.getId())
else:
reset = True
# Object was retrieve but need to be updated without recreated
# usefull when an object is only deleted by workflow.
if data_subnode is not None:
actual_xml = conduit.getXMLFromObjectWithGid(object, gid,
xml_mapping=domain.getXMLMapping(force=True))
# use gid to compare because their ids can be different
data_subnode = conduit.replaceIdFromXML(data_subnode, 'gid', gid)
# produce xupdate
data_subnode = self.getXupdateObject(data_subnode, actual_xml)
conflict_list.extend(conduit.updateNode(
xml=data_subnode,
object=object,
previous_xml=actual_xml,
force=force,
simulate=simulate,
reset=reset))
xml_object = conduit.getXMLFromObjectWithId(object,
xml_mapping=domain.getXMLMapping())
signature.setTempXML(xml_object)
if object is not None:
#LOG('applyActionList', DEBUG, 'addNode, found the object')
if reset:
#After a reset we want copy the LAST XML view on Signature.
#this implementation is not sufficient, need to be improved.
if not isinstance(xml_object, str):
xml_object = etree.tostring(xml_object, encoding='utf-8',
pretty_print=True)
else:
xml_object = conduit.getXMLFromObjectWithId(object,
xml_mapping=domain.getXMLMapping())
signature.setStatus(self.SYNCHRONIZED)
#signature.setId(object.getId())
signature.setPath(object.getPhysicalPath())
signature.setXML(xml_object)
xml_confirmation_list.append(self.SyncMLConfirmation(
cmd_id=cmd_id,
cmd='Add',
sync_code=self.ITEM_ADDED,
remote_xml=action))
cmd_id +=1
elif action.xpath('local-name()') == 'Replace':
#LOG('applyActionList', DEBUG, 'object: %s will be updated...' % str(object))
if object is not None:
#LOG('applyActionList', DEBUG, 'object: %s will be updated...' % object.id)
signature = subscriber.getSignatureFromGid(gid)
#LOG('applyActionList', DEBUG, 'previous signature: %s' % str(signature))
previous_xml = signature.getXML()
if previous_xml:
# can be None
previous_xml = conduit.replaceIdFromXML(previous_xml, 'gid', gid)
conflict_list += conduit.updateNode(
xml=data_subnode,
object=object,
previous_xml=previous_xml,
force=force,
simulate=simulate)
xml_object = conduit.getXMLFromObjectWithId(object,
xml_mapping=domain.getXMLMapping())
signature.setTempXML(xml_object)
if conflict_list:
status_code = self.CONFLICT
signature.setStatus(self.CONFLICT)
signature.setConflictList(signature.getConflictList()+conflict_list)
data_subnode_string = etree.tostring(data_subnode, encoding='utf-8')
signature.setPartialXML(data_subnode_string)
elif not simulate:
signature.setStatus(self.SYNCHRONIZED)
xml_confirmation_list.append(self.SyncMLConfirmation(
cmd_id=cmd_id,
cmd='Replace',
sync_code=status_code,
remote_xml=action))
cmd_id += 1
if simulate:
# This means we are on the publisher side and we want to store
# the xupdate from the subscriber and we also want to generate
# the current xupdate from the last synchronization
data_subnode_string = etree.tostring(data_subnode, encoding='utf-8')
#LOG('applyActionList, subscriber_xupdate:', TRACE, data_subnode_string)
signature.setSubscriberXupdate(data_subnode_string)
elif action.xpath('local-name()') == 'Delete':
#LOG("applyactionlist delete", INFO, "")
object_id = signature.getId()
#LOG('applyActionList Delete on : ', DEBUG, (signature.getId(), subscriber.getObjectFromGid(object_id)))
if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
data_subnode = self.getDataText(action)
else:
data_subnode = self.getDataSubNode(action)
#LOG('applyActionList, object gid to delete :', INFO, subscriber.getObjectFromGid(object_id))
document = subscriber.getObjectFromGid(object_id)
if document is not None:
#if the object exist:
conduit.deleteNode(
xml=data_subnode,
object=destination,
object_id=document.getId())
subscriber.delSignature(gid)
xml_confirmation_list.append(self.SyncMLConfirmation(
cmd_id=cmd_id,
cmd='Delete',
sync_code=status_code,
remote_xml=action))
else: # We want to retrieve more data
signature.setStatus(self.PARTIAL)
signature.appendPartialXML(data)
#LOG('applyActionList', INFO, 'waiting more data for :%s' % signature.getId())
#LOG('applyActionList', INFO, 'waiting more data for :%s' % object.getPath())
#LOG('applyActionList', INFO, data)
xml_confirmation_list.append(self.SyncMLConfirmation(
cmd_id=cmd_id,
cmd="%s" % action.xpath('name()'),
sync_code=self.WAITING_DATA,
remote_xml=action))
if conflict_list and signature is not None:
# We had a conflict
signature.setStatus(self.CONFLICT)
return (xml_confirmation_list, has_next_action, cmd_id)
def applyStatusList(self, subscriber=None, remote_xml=None):
"""
This read a list of status list (ie syncml confirmations).
This method have to change status codes on signatures
"""
status_list = self.getSyncBodyStatusList(remote_xml)
has_status_list = False
destination_waiting_more_data = False
for status in status_list:
if not status['code']:
continue
status_cmd = status['cmd']
object_gid = status['source']
if not object_gid:
object_gid = status['target']
status_code = int(status['code'])
signature = subscriber.getSignatureFromGid(object_gid)
if signature is None and\
not(subscriber.getSynchronizeWithERP5Sites()):
#the client give his id but not the gid
signature = subscriber.getSignatureFromRid(object_gid)
if status_cmd in ('Add', 'Replace',):
has_status_list = True
if status_code == self.CHUNK_OK:
destination_waiting_more_data = True
signature.setStatus(self.PARTIAL)
elif status_code == self.CONFLICT:
signature.setStatus(self.CONFLICT)
elif status_code == self.CONFLICT_MERGE:
# We will have to apply the update, and we should not care
# about conflicts, so we have to force the update
signature.setStatus(self.NOT_SYNCHRONIZED)
signature.setForce(1)
elif status_code == self.CONFLICT_CLIENT_WIN:
# The server was agree to apply our updates, nothing to do
signature.setStatus(self.SYNCHRONIZED)
elif status_code in (self.SUCCESS, self.ITEM_ADDED):
signature.setStatus(self.SYNCHRONIZED)
elif status_cmd == 'Delete':
has_status_list = True
if status_code == self.SUCCESS:
if signature is not None:
subscriber.delSignature(signature.getGid())
return (destination_waiting_more_data, has_status_list)
class XMLSyncUtils(XMLSyncUtilsMixin):
def Sync(self, id, msg=None, RESPONSE=None):
......@@ -1188,462 +354,4 @@ class XMLSyncUtils(XMLSyncUtilsMixin):
"""
pass
def getConduitByName(self, conduit_name):
"""
Get Conduit Object by given name.
The Conduit can be located in Any Products according to naming Convention
Products.<Product Name>.Conduit.<Conduit Module> ,if conduit_name equal module's name.
By default Conduit must be defined in Products.ERP5SyncML.Conduit.<Conduit Module>
"""
from Products.ERP5SyncML import Conduit
if conduit_name.startswith('Products'):
path = conduit_name
conduit_name = conduit_name.split('.')[-1]
conduit_module = __import__(path, globals(), locals(), [''])
conduit = getattr(conduit_module, conduit_name)()
else:
conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]),
globals(), locals(), [''])
conduit = getattr(conduit_module, conduit_name)()
return conduit
def SyncModif(self, domain, remote_xml):
"""
Modification Message, this is used after the first
message in order to send modifications.
Send the server modification, this happens after the Synchronization
initialization
"""
has_response = False #check if syncmodif replies to this messages
cmd_id = 1 # specifies a SyncML message-unique command identifier
#LOG('SyncModif', DEBUG, 'Starting... domain: %s' % domain.getId())
# Get informations from the header
xml_header = remote_xml[0]
#FIXME to apply a DTD or schema
if xml_header.xpath('local-name()') != "SyncHdr":
LOG('SyncModif', INFO, 'This is not a SyncML Header')
raise ValueError, "Sorry, This is not a SyncML Header"
subscriber = domain # If we are the client, this is fine
simulate = False # used by applyActionList, should be False for client
if domain.domain_type == self.PUB:
simulate = True
subscription_url = self.getSubscriptionUrlFromXML(xml_header)
subscriber = domain.getSubscriber(subscription_url)
# We have to check if this message was not already, this can be dangerous
# to update two times the same object
message_id = self.getMessageIdFromXml(remote_xml)
correct_message = subscriber.checkCorrectRemoteMessageId(message_id)
if not correct_message: # We need to send again the message
LOG('SyncModif, no correct message:', INFO, "sending again...")
last_xml = subscriber.getLastSentMessage()
LOG("SyncModif last_xml :", INFO, last_xml)
remote_xml = etree.tostring(remote_xml, encoding='utf-8',
xml_declaration=True,
pretty_print=True)
LOG("SyncModif remote_xml :", INFO, remote_xml)
if last_xml:
has_response = True
if domain.domain_type == self.PUB: # We always reply
self.sendResponse(
from_url=domain.publication_url,
to_url=subscriber.subscription_url,
sync_id=domain.getTitle(),
xml=last_xml, domain=domain,
content_type=domain.getSyncContentType())
elif domain.domain_type == self.SUB:
self.sendResponse(
from_url=domain.subscription_url,
to_url=domain.publication_url,
sync_id=domain.getTitle(),
xml=last_xml,
domain=domain,
content_type=domain.getSyncContentType())
return {'has_response':has_response, 'xml':last_xml}
subscriber.setLastSentMessage('')
# First apply the list of status codes
(destination_waiting_more_data, has_status_list) = self.applyStatusList(
subscriber=subscriber,
remote_xml=remote_xml)
alert_code = self.getAlertCodeFromXML(remote_xml)
# Import the conduit and get it
conduit = self.getConduitByName(subscriber.getConduit())
# Then apply the list of actions
(xml_confirmation_list, has_next_action, cmd_id) = self.applyActionList(
cmd_id=cmd_id,
domain=domain,
subscriber=subscriber,
remote_xml=remote_xml,
conduit=conduit, simulate=simulate)
xml = E.SyncML()
# syncml header
if domain.domain_type == self.PUB:
xml.append(self.SyncMLHeader(
subscriber.getSessionId(),
subscriber.incrementMessageId(),
subscriber.getSubscriptionUrl(),
domain.getPublicationUrl()))
elif domain.domain_type == self.SUB:
xml.append(self.SyncMLHeader(
domain.getSessionId(), domain.incrementMessageId(),
domain.getPublicationUrl(),
domain.getSubscriptionUrl()))
# syncml body
sync_body = E.SyncBody()
xml.append(sync_body)
xml_status, cmd_id = self.SyncMLStatus(
remote_xml,
self.SUCCESS,
cmd_id,
subscriber.getNextAnchor(),
subscription=subscriber)
sync_body.extend(xml_status)
destination_url = ''
# alert message if we want more data
if destination_waiting_more_data:
sync_body.append(self.SyncMLAlert(
cmd_id,
self.WAITING_DATA,
subscriber.getTargetURI(),
subscriber.getSourceURI(),
subscriber.getLastAnchor(),
subscriber.getNextAnchor()))
# Now we should send confirmations
cmd_id_before_getsyncmldata = cmd_id
cmd_id = cmd_id+1
if domain.getActivityEnabled():
#use activities to get SyncML data.
remote_xml = etree.tostring(remote_xml, encoding='utf-8',
xml_declaration=True, pretty_print=False)
xml_tree = etree.tostring(xml, encoding='utf-8', xml_declaration=True,
pretty_print=False)
xml_confirmation_list = [etree.tostring(xml, encoding='utf-8',\
xml_declaration=True,\
pretty_print=False) for xml in \
xml_confirmation_list]
domain.activate(activity='SQLQueue',
tag=domain.getId(),
priority=self.PRIORITY).activateSyncModif(
domain_relative_url=domain.getRelativeUrl(),
remote_xml=remote_xml,
xml_tree=xml_tree,
subscriber_relative_url=subscriber.getRelativeUrl(),
cmd_id=cmd_id,
xml_confirmation_list=xml_confirmation_list,
syncml_data_list=[],
cmd_id_before_getsyncmldata=cmd_id_before_getsyncmldata,
has_status_list=has_status_list,
has_response=has_response )
return {'has_response':True, 'xml':''}
else:
result = self.getSyncMLData(domain=domain,
remote_xml=remote_xml,
subscriber=subscriber,
cmd_id=cmd_id,
xml_confirmation_list=xml_confirmation_list,
conduit=conduit,
max=None)
syncml_data_list = result['syncml_data_list']
xml_confirmation_list = result['xml_confirmation_list']
cmd_id = result['cmd_id']
return self.sendSyncModif(syncml_data_list, cmd_id_before_getsyncmldata,
subscriber, domain, xml_confirmation_list,
remote_xml, xml, has_status_list,
has_response)
def deleteRemainObjectList(self, domain, subscriber):
"""
This method allow deletion on not synchronised Objects at the end of Synchronisation session.
Usefull only after reseting in One Way Sync
"""
object_list = domain.getObjectList()
gid_list = [domain.getGidFromObject(x) for x in object_list]
domain_path = domain.getPath()
subscriber_path = subscriber.getPath()
while len(gid_list):
sliced_gid_list = [gid_list.pop() for i in gid_list[:self.MAX_OBJECTS]]
#Split List Processing in activities
self.activate(activity='SQLQueue',
tag=domain.getId(),
priority=self.PRIORITY).activateDeleteRemainObjectList(domain_path,
subscriber_path,
sliced_gid_list)
def activateDeleteRemainObjectList(self, domain_path, subscriber_path, gid_list):
"""
Execute Deletion in Activities
"""
domain = self.unrestrictedTraverse(domain_path)
subscriber = self.unrestrictedTraverse(subscriber_path)
destination = self.unrestrictedTraverse(domain.getDestinationPath())
conduit_name = subscriber.getConduit()
conduit = self.getConduitByName(conduit_name)
for gid in gid_list:
if subscriber.getSignatureFromGid(gid) is None:
object_id = b16decode(gid)
conduit.deleteObject(object=destination, object_id=object_id)
def activateSyncModif(self, **kw):
domain = self.unrestrictedTraverse(kw['domain_relative_url'])
subscriber = self.unrestrictedTraverse(kw['subscriber_relative_url'])
conduit = subscriber.getConduit()
result = self.getSyncMLData(domain=domain, subscriber=subscriber,
conduit=conduit, max=self.MAX_OBJECTS, **kw)
syncml_data_list = result['syncml_data_list']
cmd_id = result['cmd_id']
kw['syncml_data_list'] = syncml_data_list
kw['cmd_id'] = cmd_id
finished = result['finished']
if not finished:
domain.activate(activity='SQLQueue',
tag=domain.getId(),
priority=self.PRIORITY).activateSyncModif(**kw)
else:
cmd_id = result['cmd_id']
cmd_id_before_getsyncmldata = kw['cmd_id_before_getsyncmldata']
remote_xml = etree.XML(kw['remote_xml'], parser=parser)
xml_tree = etree.XML(kw['xml_tree'], parser=parser)
xml_confirmation_list = kw['xml_confirmation_list']
has_status_list = kw['has_status_list']
has_response = kw['has_response']
return self.sendSyncModif(
syncml_data_list,
cmd_id_before_getsyncmldata,
subscriber,
domain,
xml_confirmation_list,
remote_xml,
xml_tree,
has_status_list,
has_response)
def sendSyncModif(self, syncml_data_list, cmd_id_before_getsyncmldata,
subscriber, domain, xml_confirmation_list, remote_xml,
xml_tree, has_status_list, has_response):
# XXX the better is a namespace for all
namespace = self.getNamespace(xml_tree.nsmap)
sync_body = xml_tree.find('SyncBody')
if sync_body is None:
sync_body = xml_tree.xpath('syncml:SyncBody')[0]
if syncml_data_list:
sync_node = E.Sync(E.CmdID('%s' % cmd_id_before_getsyncmldata))
sync_body.append(sync_node)
target_uri = subscriber.getTargetURI()
if target_uri:
sync_node.append(E.Target(E.LocURI(target_uri)))
source_uri = subscriber.getSourceURI()
if source_uri:
sync_node.append(E.Source(E.LocURI(source_uri)))
for syncml_data in syncml_data_list:
sync_node.append(etree.XML(syncml_data, parser=parser))
for xml_confirmation in xml_confirmation_list:
if isinstance(xml_confirmation, str):
xml_confirmation = etree.XML(xml_confirmation, parser=parser)
sync_body.append(xml_confirmation)
self.sync_finished = False
if domain.domain_type == self.PUB: # We always reply
# When the publication recieved the response Final and the modification
# data is finished so the publication send the tag "Final"
if not self.checkSync(remote_xml) and not xml_confirmation_list\
and not syncml_data_list and self.checkFinal(remote_xml):
sync_body.append(E.Final())
self.sync_finished = True
xml_string = etree.tostring(xml_tree, encoding='utf-8', pretty_print=True)
subscriber.setLastSentMessage(xml_string)
self.sendResponse(
from_url=domain.publication_url,
to_url=subscriber.subscription_url,
sync_id=domain.getTitle(),
xml=xml_string,
domain=domain,
content_type=domain.getSyncContentType())
if self.sync_finished:
LOG('this is the end of the synchronisation session from PUB !!!', INFO, domain.getId())
subscriber.setAuthenticated(False)
domain.setAuthenticated(False)
has_response = True
elif domain.domain_type == self.SUB:
# the modification data is finished on the subscription so the tag
# "Final" sent to the publication
if not self.checkAlert(remote_xml) and not xml_confirmation_list\
and not syncml_data_list:
sync_body.append(E.Final())
self.sync_finished = True
xml_string = etree.tostring(xml_tree, encoding='utf-8', pretty_print=True)
if not self.sync_finished or not self.checkFinal(remote_xml):
subscriber.setLastSentMessage(xml_string)
self.sendResponse(
from_url=domain.subscription_url,
to_url=domain.publication_url,
sync_id=domain.getTitle(),
xml=xml_string, domain=domain,
content_type=domain.getSyncContentType())
has_response = True
#When the receive the final element and the sub finished synchronization
else:
if domain.isOneWayFromServer():
self.deleteRemainObjectList(domain, subscriber)
LOG('this is the end of the synchronisation session from SUB !!!', INFO, domain.getId())
domain.setAuthenticated(False)
return {'has_response':has_response, 'xml':xml_string}
def xml2wbxml(self, xml):
"""
convert xml string to wbxml using a temporary file
"""
#LOG('xml2wbxml starting ...', DEBUG, '')
import os
f = open('/tmp/xml2wbxml', 'w')
f.write(xml)
f.close()
os.system('/usr/bin/xml2wbxml -o /tmp/xml2wbxml /tmp/xml2wbxml')
f = open('/tmp/xml2wbxml', 'r')
wbxml = f.read()
f.close()
return wbxml
def wbxml2xml(self, wbxml):
"""
convert wbxml string to xml using a temporary file
"""
#LOG('wbxml2xml starting ...', DEBUG, '')
import os
f = open('/tmp/wbxml2xml', 'w')
f.write(wbxml)
f.close()
os.system('/usr/bin/wbxml2xml -o /tmp/wbxml2xml /tmp/wbxml2xml')
f = open('/tmp/wbxml2xml', 'r')
xml = f.read()
f.close()
return xml
def PubSync(self, publication_path, msg=None, RESPONSE=None, subscriber=None):
"""
This is the synchronization method for the server
"""
from Products.ERP5SyncML.Publication import Subscriber
#LOG('PubSync', DEBUG, 'Starting... publication: %s' % (publication_path))
# Read the request from the client
publication = self.unrestrictedTraverse(publication_path)
xml_client = msg
if xml_client is None:
xml_client = self.readResponse(from_url=publication.getPublicationUrl())
#LOG('PubSync', DEBUG, 'Starting... msg: %s' % str(xml_client))
result = None
if xml_client is not None:
if isinstance(xml_client, (str, unicode)):
xml_client = etree.XML(xml_client, parser=parser)
#FIXME to apply a DTD or schema
if xml_client.xpath('local-name()') != "SyncML":
LOG('PubSync', INFO, 'This is not a SyncML Message')
raise ValueError, "Sorry, This is not a SyncML Message"
alert_code = self.getAlertCodeFromXML(xml_client)
# Get informations from the header
client_header = xml_client[0]
#FIXME to apply a DTD or schema
if client_header.xpath('local-name()') != "SyncHdr":
LOG('PubSync', INFO, 'This is not a SyncML Header')
raise ValueError, "Sorry, This is not a SyncML Header"
subscription_url = self.getSubscriptionUrlFromXML(client_header)
# Get the subscriber or create it if not already in the list
subscriber = publication.getSubscriber(subscription_url)
if subscriber is None:
subscriber = Subscriber(publication.generateNewId(), subscription_url)
subscriber.setXMLMapping(publication.getXMLMapping())
subscriber.setConduit(publication.getConduit())
publication.addSubscriber(subscriber)
subscriber = subscriber.__of__(publication)
# first synchronization
result = self.PubSyncInit(publication=publication,
xml_client=xml_client,
subscriber=subscriber,
sync_type=self.SLOW_SYNC)
elif self.checkAlert(xml_client) and \
alert_code in (self.TWO_WAY, self.SLOW_SYNC, \
self.ONE_WAY_FROM_SERVER):
subscriber.setXMLMapping(publication.getXMLMapping())
subscriber.setConduit(publication.getConduit())
result = self.PubSyncInit(publication=publication,
xml_client=xml_client,
subscriber=subscriber,
sync_type=alert_code)
else:
#we log the user authenticated to do the synchronization with him
if self.checkMap(xml_client) :
self.setRidWithMap(xml_client, subscriber)
if subscriber.isAuthenticated():
uf = self.getPortalObject().acl_users
user = uf.getUserById(subscriber.getUser()).__of__(uf)
newSecurityManager(None, user)
result = self.PubSyncModif(publication, xml_client)
else:
result = self.PubSyncModif(publication, xml_client)
elif subscriber is not None:
# This looks like we are starting a synchronization after
# a conflict resolution by the user
result = self.PubSyncInit(publication=publication,
xml_client=None,
subscriber=subscriber,
sync_type=self.TWO_WAY)
if RESPONSE is not None:
RESPONSE.redirect('managePublications')
elif result is not None:
return result
def SubSync(self, subscription_path, msg=None, RESPONSE=None):
"""
This is the synchronization method for the client
"""
response = None #check if subsync replies to this messages
subscription = self.unrestrictedTraverse(subscription_path)
if msg is None and (subscription.getSubscriptionUrl()).find('file') >= 0:
msg = self.readResponse(sync_id=subscription.getSubscriptionUrl(),
from_url=subscription.getSubscriptionUrl())
if msg is None:
response = self.SubSyncInit(subscription)
else:
xml_client = msg
if isinstance(xml_client, (str, unicode)):
xml_client = etree.XML(xml_client, parser=parser)
status_list = self.getSyncBodyStatusList(xml_client)
if status_list:
status_code_syncHdr = status_list[0]['code']
if status_code_syncHdr.isdigit():
status_code_syncHdr = int(status_code_syncHdr)
#LOG('SubSync status code : ', DEBUG, status_code_syncHdr)
if status_code_syncHdr == self.AUTH_REQUIRED:
if self.checkChal(xml_client):
authentication_format, authentication_type = self.getChal(xml_client)
#LOG('SubSync auth_required :', DEBUG, 'format:%s, type:%s' % (authentication_format, authentication_type))
if authentication_format is not None and \
authentication_type is not None:
subscription.setAuthenticationFormat(authentication_format)
subscription.setAuthenticationType(authentication_type)
else:
raise ValueError, "Sorry, the server chalenge for an \
authentication, but the authentication format is not find"
LOG('SubSync', INFO, 'Authentication required')
response = self.SubSyncCred(subscription, xml_client)
elif status_code_syncHdr == self.UNAUTHORIZED:
LOG('SubSync', INFO, 'Bad authentication')
return {'has_response': False, 'xml': xml_client}
else:
response = self.SubSyncModif(subscription, xml_client)
else:
response = self.SubSyncModif(subscription, xml_client)
if RESPONSE is not None:
RESPONSE.redirect('manageSubscriptions')
else:
return response
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2002-2010 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
......@@ -30,27 +31,34 @@
and extended local roles management
"""
import sys, Permissions, os
from App.Common import package_home
this_module = sys.modules[ __name__ ]
product_path = package_home( globals() )
this_module._dtmldir = os.path.join( product_path, 'dtml' )
# Update ERP5 Globals
from Products.ERP5Type.Utils import initializeProduct, updateGlobals
import sys, Permissions
this_module = sys.modules[ __name__ ]
document_classes = updateGlobals( this_module, globals(), permissions_module = Permissions)
# Define object classes and tools
import SynchronizationTool, Publication, Subscription
object_classes = (Subscription.Subscription, Publication.Publication,Publication.Subscriber)
portal_tools = (SynchronizationTool.SynchronizationTool,)
content_classes = ()
content_constructors = ()
document_classes = updateGlobals(this_module, globals(),
permissions_module=Permissions)
import PropertySheet
import interfaces
# Finish installation
def initialize( context ):
# Import Product Components
from Tool import SynchronizationTool
import Document
# Define documents, classes, constructors and tools
object_classes = ()
content_constructors = ()
content_classes = ()
portal_tools = (SynchronizationTool.SynchronizationTool,)
# Do initialization step
initializeProduct(context, this_module, globals(),
document_module = Document,
document_classes = document_classes,
object_classes = object_classes,
portal_tools = portal_tools,
content_constructors = content_constructors,
content_classes = content_classes)
document_module=Document,
document_classes=document_classes,
object_classes=object_classes,
portal_tools=portal_tools,
content_constructors=content_constructors,
content_classes=content_classes)
# -*- coding: utf-8 -*-
from conduit import IConduit
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
......@@ -35,9 +36,9 @@ class IConduit(Interface):
- updating an object attributes from an XUpdate XML stream
(Conduits are not in charge of creating new objects which
are eventually missing in a synchronisation process)
are eventually missing in a synchronization process)
If an object has be created during a synchronisation process,
If an object has be created during a synchronization process,
the way to proceed consists in:
1- creating an empty instance of the appropriate class
......@@ -45,9 +46,9 @@ class IConduit(Interface):
2- updating that empty instance with the conduit
The first implementation of ERP5 synchronisation
The first implementation of ERP5 synchronization
will define a default location to create new objects and
a default class. This will be defined at the level of the synchronisation
a default class. This will be defined at the level of the synchronization
tool
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
......@@ -116,12 +117,36 @@ class IConduit(Interface):
"""
def getGidFromObject(object):
def getGidFromObject(object, configurable_gid_dictionary=None):
"""
return the Gid composed with the object informations
- object is the document on which for we are building the gid.
It is usefull to interogate properties of this document itself.
- configurable_gid_dictionary optional argument which is supposed to be a dictionary
with parameters usefull to build the GID.
property_list = ordered_list of properties to interrogate
prefix = string to add in first place of the outputted string
safe = True or False. True means raise an error if interrogated property is
empty or doesn't exists.
if generated GID is empty an error is allways raised
"""
def getGidFromXML(xml, namespace, gid_from_xml_list):
"""
return the Gid composed with xml informations
"""
def applyDiff(original_data, diff):
"""Patch original data with given diff
and return patched data
original_data - can be plain/text or text/xml data
diff - diff generated with difflib or xupdate
"""
def generateDiff(old_data, new_data):
"""return a diff between old_data and new_data
respecting mimetype of handled data.
text/xml => xupdate
plain/text => difflib
"""
......@@ -35,13 +35,14 @@ from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase,\
_getConversionServerDict
from AccessControl.SecurityManagement import newSecurityManager
from Products.ERP5SyncML.Conduit.ERP5DocumentConduit import ERP5DocumentConduit
from Products.ERP5SyncML.SyncCode import SyncCode
from zLOG import LOG
from base64 import b16encode
from base64 import b16encode
import transaction
from ERP5Diff import ERP5Diff
from lxml import etree
from Products.ERP5Type.tests.utils import FileUpload
from Products.ERP5SyncML import SyncMLConstant
from Products.ERP5SyncML.Tool import SynchronizationTool
from Products.ERP5SyncML.tests.testERP5SyncML import TestERP5SyncMLMixin
test_files = os.path.join(os.path.dirname(__file__), 'test_document')
FILENAME_REGULAR_EXPRESSION = "(?P<reference>[A-Z]{3,10})-\
......@@ -53,7 +54,7 @@ def makeFileUpload(name):
path = os.path.join(test_files, name)
return FileUpload(path, name)
class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
nb_objects = 10
#for objects
......@@ -119,6 +120,7 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
Return the list of business templates.
"""
return ('erp5_base',
'erp5_syncml',
'erp5_ingestion',
'erp5_ingestion_mysql_innodb_catalog',
'erp5_web',
......@@ -150,7 +152,7 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
conversion_dict = _getConversionServerDict()
default_pref.setPreferredOoodocServerAddress(conversion_dict['hostname'])
default_pref.setPreferredOoodocServerPortNumber(conversion_dict['port'])
default_pref.setPreferredDocumentFilenameRegularExpression(FILENAME_REGULAR_EXPRESSION)
default_pref.setPreferredDocumentFileNameRegularExpression(FILENAME_REGULAR_EXPRESSION)
default_pref.setPreferredDocumentReferenceRegularExpression(REFERENCE_REGULAR_EXPRESSION)
if default_pref.getPreferenceState() == 'disabled':
default_pref.enable()
......@@ -158,41 +160,41 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
def addSubscriptions(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
if portal_sync.getSubscription(self.sub_id1) is None:
portal_sync.manage_addSubscription(title=self.sub_id1,
publication_url=self.publication_url,
subscription_url=self.subscription_url['two_way'],
destination_path='/%s/document_client1' % portal_id,
source_uri='Document:',
target_uri='Document',
query= self.sub_query1,
xml_mapping=self.xml_mapping,
conduit=self.sub_conduit1,
#alert_code=SyncCode.TWO_WAY,
gpg_key='',
activity_enabled=True,
login='daniele',
password='myPassword')
sub = portal_sync.getSubscription(self.sub_id1)
self.assertTrue(sub is not None)
if self.sub_id1 not in portal_sync.objectIds():
subscription = portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id1,
url_string=self.publication_url,
subscription_url_string=self.subscription_url['two_way'],
source='document_client1',
source_reference='Document:',
destination_reference='Document',
list_method_id= self.sub_query1,
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id=self.sub_conduit1,
sync_alert_code='two_way',
is_activity_enabled=True,
user_id='daniele',
password='myPassword')
subscription.validate()
transaction.commit()
self.tic()
def addPublications(self):
portal_id = self.getPortalName()
portal_sync = self.getSynchronizationTool()
if portal_sync.getPublication(self.pub_id) is None:
portal_sync.manage_addPublication(title=self.pub_id,
publication_url=self.publication_url,
destination_path='/%s/document_server' % portal_id,
source_uri='Document',
query=self.pub_query,
xml_mapping=self.xml_mapping,
conduit=self.pub_conduit,
gpg_key='',
activity_enabled=True,
authentication_format='b64',
authentication_type='syncml:auth-basic')
pub = portal_sync.getPublication(self.pub_id)
self.assertTrue(pub is not None)
if self.pub_id not in portal_sync.objectIds():
publication = portal_sync.newContent(portal_type='SyncML Publication',
id=self.pub_id,
url_string=self.publication_url,
source='document_server',
source_reference='Document',
list_method_id=self.pub_query,
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id=self.pub_conduit,
is_activity_enabled=True,)
publication.validate()
transaction.commit()
self.tic()
def createDocumentModules(self, one_way=False):
if not hasattr(self.portal, 'document_server'):
......@@ -211,20 +213,17 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
container = self.portal,
id = 'document_client_from_server')
def clearDocumentModules(self):
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer()
if document_server is not None:
if getattr(self.portal, 'document_server', None) is not None:
self.portal._delObject(id='document_server')
if getattr(self.portal, 'document_client1', None) is not None:
self.portal._delObject(id='document_client1')
transaction.commit()
self.tic()
def clearPublicationsAndSubscriptions(self):
portal_sync = self.getSynchronizationTool()
for pub in portal_sync.getPublicationList():
portal_sync.manage_deletePublication(pub.getTitle())
for sub in portal_sync.getSubscriptionList():
portal_sync.manage_deleteSubscription(sub.getTitle())
id_list = [object_id for object_id in portal_sync.objectIds()]
portal_sync.manage_delObjects(id_list)
transaction.commit()
self.tic()
......@@ -233,21 +232,21 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
####################
def getSynchronizationTool(self):
return getattr(self.portal, 'portal_synchronizations', None)
return getattr(self.portal, 'portal_synchronizations')
def getDocumentClient1(self):
return getattr(self.portal, 'document_client1', None)
return getattr(self.portal, 'document_client1')
def getDocumentClientFromServer(self):
return getattr(self.portal, 'document_client_from_server', None)
return getattr(self.portal, 'document_client_from_server')
def getDocumentServer(self):
return getattr(self.portal, 'document_server', None)
return getattr(self.portal, 'document_server')
def getPortalId(self):
return self.portal.getId()
def login(self, quiet=0):
def login(self):
uf = self.portal.acl_users
uf._doAddUser('daniele', 'myPassword', ['Manager'], [])
uf._doAddUser('ERP5TypeTestCase', '', ['Manager'], [])
......@@ -257,18 +256,16 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
def resetSignaturePublicationAndSubscription(self):
portal_sync = self.getSynchronizationTool()
publication = portal_sync.getPublication(self.pub_id)
subscription1 = portal_sync.getSubscription(self.sub_id1)
publication.resetAllSubscribers()
subscription1.resetAllSignatures()
publication = portal_sync[self.pub_id]
subscription1 = portal_sync[self.sub_id1]
publication.resetSubscriberList()
subscription1.resetSignatureList()
subscription1.resetAnchorList()
transaction.commit()
self.tic()
def documentMultiServer(self, quiet=0):
def documentMultiServer(self):
# create different document by category documents
if not quiet:
ZopeTestCase._print('\nTest Document Multi Server')
LOG('Testing... ', 0, 'documentMultiServer')
self.createDocumentModules()
document_id = ''
document_server = self.getDocumentServer()
......@@ -289,37 +286,34 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
self.assertEqual(nb_document, len(self.ids))
return nb_document
def documentServer(self, quiet=0, one_way=False):
def createDocumentServerList(self, one_way=False):
"""
create document in document_server
"""
if not quiet:
ZopeTestCase._print('\nTest Document Server')
LOG('Testing... ', 0, 'documentServer')
self.createDocumentModules(one_way)
self.createDocumentModules(one_way)
document_id = ''
document_server = self.getDocumentServer()
if getattr(document_server, self.id1, None) is not None:
self.clearDocumentModules()
document_text = document_server.newContent(id=self.id1,\
document_text = document_server.newContent(id=self.id1,
portal_type='Text')
kw = {'reference':self.reference1, 'Version':self.version1,\
'Language':self.language1, 'Description':self.description1}
kw = {'reference': self.reference1, 'Version': self.version1,
'Language': self.language1, 'Description': self.description1}
document_text.edit(**kw)
file = makeFileUpload(self.filename_text)
document_text.edit(file=file)
transaction.commit()
self.tic()
document_pdf = document_server.newContent(id=self.id2,\
document_pdf = document_server.newContent(id=self.id2,
portal_type='PDF')
kw = {'reference':self.reference2, 'Version':self.version2,\
'Language':self.language2, 'Description':self.description2}
kw = {'reference': self.reference2, 'Version': self.version2,
'Language': self.language2, 'Description': self.description2}
document_pdf.edit(**kw)
file = makeFileUpload(self.filename_pdf)
document_pdf.edit(file=file)
transaction.commit()
self.tic()
nb_document = len(document_server.objectValues())
nb_document = len(document_server)
self.assertEqual(nb_document, 2)
return nb_document
......@@ -345,14 +339,16 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
to define it here because it is specific to the unit testing
"""
portal_sync = self.getSynchronizationTool()
subscription = portal_sync.getSubscription(id)
subscription = portal_sync[id]
publication = None
for pub in portal_sync.getPublicationList():
if pub.getPublicationUrl()==subscription.getPublicationUrl():
for pub in portal_sync.searchFolder(portal_type='SyncML Publication',
source_reference=subscription.getDestinationReference(),
validation_state='validated'):
if pub.getUrlString() == subscription.getUrlString():
publication = pub
self.assertTrue(publication is not None)
# reset files, because we do sync by files
file = open(subscription.getSubscriptionUrl()[len('file:/'):], 'w')
file = open(subscription.getSubscriptionUrlString()[len('file:/'):], 'w')
file.write('')
file.close()
file = open(self.publication_url[len('file:/'):], 'w')
......@@ -362,7 +358,7 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
self.tic()
nb_message = 1
result = portal_sync.SubSync(subscription.getPath())
while result['has_response']==1:
while result['has_response']:
portal_sync.PubSync(publication.getPath())
if self.activity_enabled:
transaction.commit()
......@@ -372,6 +368,8 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
transaction.commit()
self.tic()
nb_message += 1 + result['has_response']
transaction.commit()
self.tic()
return nb_message
def synchronizeWithBrokenMessage(self, id):
......@@ -381,14 +379,16 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
"""
portal_sync = self.getSynchronizationTool()
#portal_sync.email = None # XXX To be removed
subscription = portal_sync.getSubscription(id)
subscription = portal_sync[id]
publication = None
for publication in portal_sync.getPublicationList():
if publication.getPublicationUrl()==subscription.getSubscriptionUrl():
publication = publication
for pub in portal_sync.searchFolder(portal_type='SyncML Publication',
source_reference=subscription.getDestinationReference(),
validation_state='validated'):
if pub.getUrlString() == subscription.getUrlString():
publication = pub
self.assertTrue(publication is not None)
# reset files, because we do sync by files
file = open(subscription.getSubscriptionUrl()[len('file:/'):], 'w')
file = open(subscription.getSubscriptionUrlString()[len('file:/'):], 'w')
file.write('')
file.close()
file = open(self.publication_url[len('file:/'):], 'w')
......@@ -398,7 +398,7 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
self.tic()
nb_message = 1
result = portal_sync.SubSync(subscription.getPath())
while result['has_response']==1:
while result['has_response']:
# We do thing three times, so that we will test
# if we manage well duplicate messages
portal_sync.PubSync(publication.getPath())
......@@ -426,35 +426,37 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
transaction.commit()
self.tic()
nb_message += 1 + result['has_response']
transaction.commit()
self.tic()
return nb_message
def checkSynchronizationStateIsSynchronized(self, quiet=0):
def checkSynchronizationStateIsSynchronized(self):
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer()
for document in document_server.objectValues():
state_list = portal_sync.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], state[0].SYNCHRONIZED)
self.assertEqual(state[1], 'synchronized')
document_client1 = self.getDocumentClient1()
for document in document_client1.objectValues():
state_list = portal_sync.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], state[0].SYNCHRONIZED)
self.assertEqual(state[1], 'synchronized')
# Check for each signature that the tempXML is None
for sub in portal_sync.getSubscriptionList():
for m in sub.getSignatureList():
self.assertEquals(m.getTempXML(), None)
self.assertEquals(m.getPartialXML(), None)
for pub in portal_sync.getPublicationList():
for sub in pub.getSubscriberList():
for m in sub.getSignatureList():
self.assertEquals(m.getPartialXML(), None)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
for m in sub.contentValues():
self.assertEquals(m.getTemporaryData(), None)
self.assertEquals(m.getPartialData(), None)
for pub in portal_sync.contentValues(portal_type='SyncML Publication'):
for sub in pub.contentValues(portal_type='SyncML Subscription'):
for m in sub.contentValues():
self.assertEquals(m.getPartialData(), None)
def checkFirstSynchronization(self, nb_document=0):
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync.getSubscription(self.sub_id1)
self.assertEqual(len(subscription1.getObjectList()), nb_document)
subscription1 = portal_sync[self.sub_id1]
self.assertEqual(len(subscription1), nb_document)
document_server = self.getDocumentServer() # We also check we don't
# modify initial ob
doc1_s = document_server._getOb(self.id1)
......@@ -479,11 +481,11 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
self.assertEqual(document_c.getFilename(), self.filename_text)
self.assertEquals(self.size_filename_text, document_c.get_size())
self.assertXMLViewIsEqual(self.sub_id1, doc1_s, document_c)
self.assertXMLViewIsEqual(self.sub_id1, doc2_s,\
document_client1._getOb(self.id2))
self.assertXMLViewIsEqual(self.sub_id1, doc2_s,
document_client1._getOb(self.id2))
def checkDocument(self, id=id, document=None, filename=None,
size_filename=None, reference='P-SYNCML.Text',
size_filename=None, reference='P-SYNCML.Text',
portal_type='Text', version='001', language='en',
description=''):
"""
......@@ -500,7 +502,7 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
self.assertEquals(size_filename, document.get_size())
else:
self.fail("Document is None for check these informations")
def checkXMLsSynchronized(self):
document_server = self.getDocumentServer()
document_client1 = self.getDocumentClient1()
......@@ -512,7 +514,7 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
def checkFirstSynchronizationWithMultiDocument(self, nb_document=0):
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync.getSubscription(self.sub_id1)
subscription1 = portal_sync[self.sub_id1]
self.assertEqual(len(subscription1.getObjectList()), nb_document)
document_server = self.getDocumentServer()
document_client1 = self.getDocumentClient1()
......@@ -530,40 +532,7 @@ class TestERP5DocumentSyncMLMixin(ERP5TypeTestCase):
reference=reference,
filename=self.filename_odt,
size_filename=self.size_filename_odt)
self.checkXMLsSynchronized()
def assertXMLViewIsEqual(self, sub_id, object_pub=None, object_sub=None,
force=False):
"""
Check the equality between two xml objects with gid as id
"""
portal_sync = self.getSynchronizationTool()
subscription = portal_sync.getSubscription(sub_id)
publication = portal_sync.getPublication(self.pub_id)
gid_pub = publication.getGidFromObject(object_pub)
gid_sub = publication.getGidFromObject(object_sub)
self.assertEqual(gid_pub, gid_sub)
conduit = ERP5DocumentConduit()
xml_pub = conduit.getXMLFromObjectWithGid(object=object_pub, gid=gid_pub,
xml_mapping=publication.getXMLMapping())
#if One Way From Server there is not xml_mapping for subscription
xml_sub = conduit.getXMLFromObjectWithGid(object=object_sub, gid=gid_sub,
xml_mapping=subscription.getXMLMapping(force))
erp5diff = ERP5Diff()
erp5diff.compare(xml_pub, xml_sub)
result = etree.XML(erp5diff.outputString())
identity = True
for update in result:
#XXX edit workflow is not replaced, so discard workflow checking
if 'workflow' not in update.get('select', ''):
identity = False
break
if not identity:
self.fail('diff between %s and %s \nxupdate:\n%s' %\
(object_pub.getRelativeUrl(),
object_sub.getRelativeUrl(),
etree.tostring(result, pretty_print=True)))
self.checkXMLsSynchronized()
class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
......@@ -572,79 +541,60 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
"""
return "ERP5 Document SyncML"
def setupPublicationAndSubscriptionIdGenerator(self, quiet=0):
def setupPublicationAndSubscriptionIdGenerator(self):
portal_sync = self.getSynchronizationTool()
sub1 = portal_sync.getSubscription(self.sub_id1)
pub = portal_sync.getPublication(self.pub_id)
pub.setSynchronizationIdGenerator('generateNewId')
sub1.setSynchronizationIdGenerator('generateNewId')
sub1 = portal_sync[self.sub_id1]
pub = portal_sync[self.pub_id]
pub.setSynchronizationIdGeneratorMethodId('generateNewId')
sub1.setSynchronizationIdGeneratorMethodId('generateNewId')
def checkSynchronizationStateIsConflict(self, quiet=0,
portal_type='Text'):
def checkSynchronizationStateIsConflict(self, portal_type='Text'):
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer()
for document in document_server.objectValues():
if document.getId()==self.id1:
state_list = portal_sync.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], state[0].CONFLICT)
self.assertEqual(state[1], 'conflict')
document_client1 = self.getDocumentClient1()
for document in document_client1.objectValues():
if document.getId()==self.id1:
state_list = portal_sync.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], state[0].CONFLICT)
self.assertEqual(state[1], 'conflict')
# make sure sub object are also in a conflict mode
document = document_client1._getOb(self.id1)
state_list = portal_sync.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], state[0].CONFLICT)
self.assertEqual(state[1], 'conflict')
def test_01_GetSynchronizationList(self, quiet=0):
def test_01_GetSynchronizationList(self):
# This test the getSynchronizationList, ie,
# We want to see if we retrieve both the subscription
# and the publication
if not quiet:
ZopeTestCase._print('\nTest getSynchronizationList ')
LOG('Testing... ', 0, 'test_01_GetSynchronizationList')
portal_sync = self.getSynchronizationTool()
synchronization_list = portal_sync.getSynchronizationList()
synchronization_list = portal_sync.contentValues()
self.assertEqual(len(synchronization_list), self.nb_synchronization)
def test_02_FirstSynchronization(self, quiet=0):
def test_02_FirstSynchronization(self):
# We will try to populate the folder document_client1
# with the data form document_server
if not quiet:
ZopeTestCase._print('\nTest First Synchronization ')
LOG('Testing... ', 0, 'test_02_FirstSynchronization')
nb_document = self.documentServer(quiet=1)
nb_document = self.createDocumentServerList()
portal_sync = self.getSynchronizationTool()
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(), SyncCode.SLOW_SYNC)
document_server = self.getDocumentServer()
doc1_s = document_server._getOb(self.id1)
self.assertEqual(doc1_s.getFilename(), self.filename_text)
self.assertEquals(self.size_filename_text, doc1_s.get_size())
doc2_s = document_server._getOb(self.id2)
self.assertEqual(doc2_s.getFilename(), self.filename_pdf)
self.assertEquals(self.size_filename_pdf, doc2_s.get_size())
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
# Synchronize the first client
nb_message1 = self.synchronize(self.sub_id1)
for sub in portal_sync.getSubscriptionList():
if sub.getTitle() == self.sub_id1:
self.assertEquals(sub.getSynchronizationType(), SyncCode.TWO_WAY)
else:
self.assertEquals(sub.getSynchronizationType(), SyncCode.SLOW_SYNC)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
self.assertEqual(nb_message1, self.nb_message_first_synchronization)
self.checkSynchronizationStateIsSynchronized()
self.checkFirstSynchronization(nb_document=nb_document)
def test_03_UpdateSimpleData(self, quiet=0):
if not quiet:
ZopeTestCase._print('\nTest Update Simple Data ')
LOG('Testing... ', 0, 'test_03_UpdateSimpleData')
def test_03_UpdateSimpleData(self):
# Add two objects
self.test_02_FirstSynchronization(quiet=1)
self.test_02_FirstSynchronization()
# First we do only modification on server
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer()
......@@ -695,16 +645,13 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
self.assertEqual(document_s.getBaseData(), document_c.getBaseData())
self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c)
def test_04_DeleteObject(self, quiet=0):
def test_04_DeleteObject(self):
"""
We will do a first synchronization, then delete an object on both
sides, and we will see if nothing is left on the server and also
on the two clients
"""
self.test_02_FirstSynchronization(quiet=1)
if not quiet:
ZopeTestCase._print('\nTest Delete Object ')
LOG('Testing... ', 0, 'test_04_DeleteObject')
self.test_02_FirstSynchronization()
document_server = self.getDocumentServer()
document_server.manage_delObjects(self.id1)
document_client1 = self.getDocumentClient1()
......@@ -714,35 +661,29 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
portal_sync = self.getSynchronizationTool()
publication = portal_sync.getPublication(self.pub_id)
subscription1 = portal_sync.getSubscription(self.sub_id1)
publication = portal_sync[self.pub_id]
subscription1 = portal_sync[self.sub_id1]
self.assertEqual(len(publication.getObjectList()), 0)
self.assertEqual(len(subscription1.getObjectList()), 0)
def test_05_FirstMultiSynchronization(self, quiet=0):
def test_05_FirstMultiSynchronization(self):
#Add document on the server and first synchronization for client
if not quiet:
ZopeTestCase._print('\nTest First Multi Synchronization ')
LOG('Testing... ', 0, 'test_05_FirstMultiSynchronization')
nb_document = self.documentMultiServer(quiet=1)
nb_document = self.documentMultiServer()
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer()
document_client = self.getDocumentClient1()
nb_message1 = self.synchronize(self.sub_id1)
self.assertNotEqual(nb_message1, 6)
# It has transmitted some object
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(), SyncCode.TWO_WAY)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
self.checkSynchronizationStateIsSynchronized()
self.checkFirstSynchronizationWithMultiDocument(nb_document=nb_document)
def test_06_UpdateMultiData(self, quiet=0):
# Add various data in server
def test_06_UpdateMultiData(self):
# Add various data in server
# modification in client and server for synchronize
if not quiet:
ZopeTestCase._print('\nTest Update Multi Data ')
LOG('Testing... ', 0, 'test_06_UpdateMultiData')
self.test_05_FirstMultiSynchronization(quiet=1)
self.test_05_FirstMultiSynchronization()
# Side server modification gid of a text document
document_server = self.getDocumentServer()
document_client1= self.getDocumentClient1()
......@@ -826,25 +767,22 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
# Check the XMLs
self.checkXMLsSynchronized()
def test_07_SynchronizeWithStrangeIdGenerator(self, quiet=0):
def test_07_SynchronizeWithStrangeIdGenerator(self):
"""
By default, the synchronization process use the id in order to
recognize objects (because by default, getGid==getId. Here, we will see
recognize objects (because by default, getGid==getId. Here, we will see
if it also works with a somewhat strange getGid
"""
if not quiet:
ZopeTestCase._print('\nTest Synchronize With Strange Gid ')
LOG('Testing... ', 0, 'test_07_SynchronizeWithStrangeIdGenerator')
self.setupPublicationAndSubscriptionIdGenerator(quiet=1)
nb_document = self.documentServer(quiet=1)
self.setupPublicationAndSubscriptionIdGenerator()
nb_document = self.createDocumentServerList()
# This will test adding object
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync.getSubscription(self.sub_id1)
self.assertEqual(len(subscription1.getObjectList()), nb_document)
publication = portal_sync.getPublication(self.pub_id)
self.assertEqual(len(publication.getObjectList()), nb_document)
subscription1 = portal_sync[self.sub_id1]
self.assertEqual(len(subscription1), nb_document)
publication = portal_sync[self.pub_id]
self.assertEqual(len(publication['1']), nb_document)
gid = self.reference1 + '-' + self.version1 + '-' + self.language1 # ie the title ''
gid = b16encode(gid)
document_c1 = subscription1.getObjectFromGid(gid)
......@@ -855,6 +793,8 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
self.assertEqual(id_s, self.id1)
# This will test updating object
document_s.setDescription(self.description3)
transaction.commit()
self.tic()
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
self.assertEqual(document_s.getDescription(), self.description3)
......@@ -879,16 +819,13 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
self.assertEqual(document_s.getDescription(), self.description3)
self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c1)
def test_08_MultiNodeConflict(self, quiet=0):
def test_08_MultiNodeConflict(self):
"""
We will create conflicts with 3 differents nodes, and we will
solve it by taking one full version of documents.
"""
#not conflict because is gid
self.test_02_FirstSynchronization(quiet=1)
if not quiet:
ZopeTestCase._print('\nTest Multi Node Conflict ')
LOG('Testing... ', 0, 'test_08_MultiNodeConflict')
self.test_02_FirstSynchronization()
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer()
document_s = document_server._getOb(self.id1)
......@@ -905,31 +842,31 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
document_c1.edit(**kw)
file = makeFileUpload(self.filename_odt)
document_c1.edit(file=file)
#document_c1.convertToBaseFormat()
transaction.commit()
self.tic()
self.synchronize(self.sub_id1)
conflict_list = portal_sync.getConflictList()
self.assertEqual(len(conflict_list), 9)
self.assertEqual(sorted([x.getKeyword() for x in conflict_list]),
self.assertEqual(sorted([x.getPropertyId() for x in conflict_list]),
['base_data', 'content_md5', 'content_type',
'data', 'description', 'filename', 'short_title',
'data', 'description', 'filename', 'short_title',
'size', 'title'])
# check if we have the state conflict on all clients
self.checkSynchronizationStateIsConflict()
# we will take :
# description et file on document_server
# short_title on document_client1
for conflict in conflict_list :
for conflict in conflict_list :
subscriber = conflict.getSubscriber()
property = conflict.getPropertyId()
resolved = False
if property == 'description':
if subscriber.getSubscriptionUrl() == self.publication_url:
if subscriber.getSubscriptionUrlString() == self.publication_url:
resolved = True
conflict.applySubscriberValue()
if property == 'short_title':
if subscriber.getSubscriptionUrl() == self.subscription_url['two_way']:
if subscriber.getSubscriptionUrlString() == self.subscription_url['two_way']:
resolved = True
conflict.applySubscriberValue()
if not resolved:
......@@ -944,38 +881,8 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
document_s = document_server._getOb(self.id1)
document_c = document_client1._getOb(self.id1)
self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c1)
# the workflow has one more "workflow" in document_c1
#self.synchronize(self.sub_id1)
#self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c1)
def test_09_SynchronizeWorkflowHistory(self, quiet=0):
"""
We will do a synchronization, then we will edit two times
the object on the server, then two times the object on the
client, and see if the global history as 4 more actions.
"""
self.test_02_FirstSynchronization(quiet=1)
if not quiet:
ZopeTestCase._print('\nTest Synchronize WorkflowHistory ')
LOG('Testing... ', 0, 'test_09_SynchronizeWorkflowHistory')
document_server = self.getDocumentServer()
document_s = document_server._getOb(self.id1)
document_client1 = self.getDocumentClient1()
document_c = document_client1._getOb(self.id1)
kw1 = {'short_title':self.short_title1}
kw2 = {'short_title':self.short_title2}
len_wf = len(document_s.workflow_history[self.workflow_id])
document_s.edit(**kw2)
document_c.edit(**kw2)
document_s.edit(**kw1)
document_c.edit(**kw1)
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c)
self.assertEqual(len(document_s.workflow_history[self.workflow_id]), len_wf)
self.assertEqual(len(document_c.workflow_history[self.workflow_id]), len_wf)
def test_10_BrokenMessage(self, quiet=0):
def test_10_BrokenMessage(self):
"""
With http synchronization, when a message is not well
received, then we send message again, we want to
......@@ -984,17 +891,14 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
If we want to make this test more intersting, it is
better to split messages
"""
if not quiet:
ZopeTestCase._print('\nTest Broken Message ')
LOG('Testing... ', 0, 'test_10_BrokenMessage')
previous_max_lines = SyncCode.MAX_LINES
SyncCode.MAX_LINES = 10
nb_document = self.documentServer(quiet=1)
previous_max_lines = SynchronizationTool.MAX_LEN
SynchronizationTool.MAX_LEN = 1 << 8
nb_document = self.createDocumentServerList()
# Synchronize the first client
nb_message1 = self.synchronizeWithBrokenMessage(self.sub_id1)
#self.failUnless(nb_message1==self.nb_message_first_synchronization)
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync.getSubscription(self.sub_id1)
subscription1 = portal_sync[self.sub_id1]
self.assertEqual(len(subscription1.getObjectList()), nb_document)
document_server = self.getDocumentServer() # We also check we don't
# modify initial ob
......@@ -1007,50 +911,41 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
self.assertEqual(document_s.getFilename(), self.filename_text)
self.assertEquals(self.size_filename_text, document_c.get_size())
self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c)
SyncCode.MAX_LINES = previous_max_lines
SynchronizationTool.MAX_LEN = previous_max_lines
def test_11_AddOneWaySubscription(self, quiet=0):
if not quiet:
ZopeTestCase._print('\nTest Add One Way Subscription ')
LOG('Testing... ', 0, 'test_11_AddOneWaySubscription')
def addOneWaySyncFromServerSubscription(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
portal_sync.manage_addSubscription(title=self.sub_id_from_server,
publication_url=self.publication_url,
subscription_url=self.subscription_url['from_server'],
destination_path='/%s/document_client_from_server' % portal_id,
source_uri='Document',
target_uri='Document',
query='objectValues',
xml_mapping=self.xml_mapping,
conduit='ERP5DocumentConduit',
gpg_key='',
activity_enabled=True,
alert_code = SyncCode.ONE_WAY_FROM_SERVER,
login = 'daniele',
password = 'myPassword')
sub = portal_sync.getSubscription(self.sub_id_from_server)
self.assertTrue(sub.isOneWayFromServer())
self.failUnless(sub is not None)
portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id_from_server,
url_string=self.publication_url,
subscription_url_string=self.subscription_url['from_server'],
source='document_client_from_server',
source_reference='DocumentSubscription',
destination_reference='Document',
list_method_id='objectValues',
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id='ERP5DocumentConduit',
is_activity_enabled=True,
syncml_alert_code='one_way_from_server',
user_id='daniele',
password='myPassword')
def test_12_OneWaySync(self, quiet=0):
def test_12_OneWaySyncFromServer(self):
"""
We will test if we can synchronize only from to server to the client.
We want to make sure in this case that all modifications on the client
will not be taken into account.
"""
if not quiet:
ZopeTestCase._print('\nTest One Way Sync ')
LOG('Testing... ', 0, 'test_12_OneWaySync')
self.test_11_AddOneWaySubscription(quiet=1)
nb_document = self.documentServer(quiet=1, one_way=True)
self.addOneWaySyncFromServerSubscription()
nb_document = self.createDocumentServerList(one_way=True)
portal_sync = self.getSynchronizationTool()
sub_from_server = portal_sync.getSubscription(self.sub_id_from_server)
self.assertEquals(sub_from_server.getSynchronizationType(), SyncCode.SLOW_SYNC)
sub_from_server = portal_sync[self.sub_id_from_server]
self.assertEquals(sub_from_server.getSyncmlAlertCode(), 'one_way_from_server')
# First do the sync from the server to the client
nb_message1 = self.synchronize(self.sub_id_from_server)
sub_from_server = portal_sync.getSubscription(self.sub_id_from_server)
self.assertEquals(sub_from_server.getSynchronizationType(), SyncCode.ONE_WAY_FROM_SERVER)
sub_from_server = portal_sync[self.sub_id_from_server]
self.assertEquals(sub_from_server.getSyncmlAlertCode(), 'one_way_from_server')
self.assertEquals(nb_message1, self.nb_message_first_synchronization)
self.assertEquals(len(sub_from_server.getObjectList()), nb_document)
document_server = self.getDocumentServer() # We also check we don't
......@@ -1067,8 +962,8 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
# is synchronization from only one way
file = makeFileUpload(self.filename_odt)
document_c.edit(file=file)
#document_c.convertToBaseFormat()
kw = {'short_title' : self.short_title2}
kw = {'short_title' : self.short_title2}
document_s.edit(**kw)
transaction.commit()
self.tic()
......@@ -1082,7 +977,7 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
self.assertEquals(document_c.getShortTitle(), self.short_title2)
self.assertEqual(document_s.getFilename(), self.filename_text)
self.assertEquals(self.size_filename_text, document_s.get_size())
self.assertEquals(document_s.getShortTitle(), self.short_title2)
self.assertEquals(document_s.getShortTitle(), self.short_title2)
#reset for refresh sync
#after synchronize, the client object retrieve value of server
......@@ -1090,12 +985,11 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
nb_message1 = self.synchronize(self.sub_id_from_server)
self.assertEqual(document_c.getFilename(), self.filename_text)
self.assertEquals(self.size_filename_text, document_c.get_size())
self.assertEquals(document_c.getShortTitle(), self.short_title2)
self.assertEquals(document_c.getShortTitle(), self.short_title2)
self.checkSynchronizationStateIsSynchronized()
document_s = document_server._getOb(self.id1)
document_c = document_client1._getOb(self.id1)
# FIXME we have to manage workflow
#self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c, force=1)
self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c, force=1)
def test_suite():
suite = unittest.TestSuite()
......
......@@ -32,16 +32,22 @@ from Testing import ZopeTestCase
from Products.ERP5Type.tests.runUnitTest import tests_home
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from AccessControl.SecurityManagement import newSecurityManager
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from Products.ERP5SyncML.SyncCode import SyncCode
from zLOG import LOG
from base64 import b64encode, b64decode, b16encode, b16decode
import transaction
from ERP5Diff import ERP5Diff
from lxml import etree
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from Products.ERP5SyncML import SyncMLConstant
from Products.ERP5SyncML.XMLSyncUtils import encode, decode,\
isDecodeEncodeTheSame
from Products.ERP5SyncML.XMLSyncUtils import getConduitByName
from Products.ERP5SyncML.SyncMLConstant import MAX_LEN
from Products.ERP5SyncML.Tool import SynchronizationTool
class TestERP5SyncMLMixin:
class TestERP5SyncMLMixin(ERP5TypeTestCase):
# Different variables used for this test
workflow_id = 'edit_workflow'
......@@ -78,7 +84,7 @@ class TestERP5SyncMLMixin:
nb_publication = 1
nb_synchronization = 3
nb_message_first_synchronization = 10
nb_message_first_sync_max_lines = 10
nb_message_first_sync_max_lines = 18
_subscription_url1 = tests_home + '/sync_client1'
_subscription_url2 = tests_home + '/sync_client2'
_publication_url = tests_home + '/sync_server'
......@@ -115,7 +121,7 @@ class TestERP5SyncMLMixin:
/person_client1 : empty
/person_client2 : empty
"""
return ('erp5_base',)
return ('erp5_base', 'erp5_syncml',)
def getSynchronizationTool(self):
return getattr(self.getPortal(), 'portal_synchronizations', None)
......@@ -132,87 +138,82 @@ class TestERP5SyncMLMixin:
def getPortalId(self):
return self.getPortal().getId()
def login(self, quiet=0):
def login(self):
uf = self.getPortal().acl_users
uf._doAddUser('fab', 'myPassword', ['Manager'], [])
uf._doAddUser('ERP5TypeTestCase', '', ['Manager'], [])
uf._doAddUser('syncml', '', ['Manager'], [])
user = uf.getUserById('fab').__of__(uf)
newSecurityManager(None, user)
def initPersonModule(self, quiet=0, run=0):
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Init Person Module')
LOG('Testing... ',0,'initPersonModule')
def initPersonModule(self):
self.login()
portal = self.getPortal()
if not hasattr(portal,'person_server'):
if getattr(portal, 'person_server', None) is None:
portal.portal_types.constructContent(type_name = 'Person Module',
container = portal,
id = 'person_server')
if not hasattr(portal,'person_client1'):
if getattr(portal, 'person_client1', None) is None:
portal.portal_types.constructContent(type_name = 'Person Module',
container = portal,
id = 'person_client1')
if not hasattr(portal,'person_client2'):
if getattr(portal, 'person_client2', None) is None:
portal.portal_types.constructContent(type_name = 'Person Module',
container = portal,
id = 'person_client2')
def populatePersonServer(self, quiet=0, run=0):
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Populate Person Server ')
LOG('Testing... ',0,'populatePersonServer')
def populatePersonServer(self):
self.login()
portal = self.getPortal()
self.initPersonModule(quiet=1, run=1)
self.initPersonModule()
person_server = self.getPersonServer()
person1 = person_server.newContent(id=self.id1, portal_type='Person')
kw = {'first_name':self.first_name1,'last_name':self.last_name1,
'description':self.description1,
'default_telephone_text': 'call me at this phone number',
'default_career_title': 'My Career Step'}
person1.edit(**kw)
person2 = person_server.newContent(id=self.id2, portal_type='Person')
kw = {'first_name':self.first_name2,'last_name':self.last_name2,
'description':self.description2,
'default_telephone_text': 'call me at this phone number',
'default_career_title': 'My Career Step'}
person2.edit(**kw)
nb_person = len(person_server.objectValues())
self.assertEqual(nb_person, 2)
if getattr(person_server, self.id1, None) is None:
person1 = person_server.newContent(id=self.id1, portal_type='Person',
first_name=self.first_name1,
last_name=self.last_name1,
description=self.description1)
if getattr(person_server, self.id2, None) is None:
person2 = person_server.newContent(id=self.id2,
portal_type='Person',
first_name=self.first_name2,
last_name=self.last_name2,
description=self.description2)
nb_person = len(person_server)
self.assertEquals(nb_person, 2)
return nb_person
def populatePersonClient1(self, quiet=0, run=0):
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Populate Person Client 1 ')
LOG('Testing... ',0,'populatePersonClient1')
def populatePersonClient1(self):
self.login()
portal = self.getPortal()
self.initPersonModule(quiet=1, run=1)
self.initPersonModule()
person_client = self.getPersonClient1()
for id in range(1, 60):
person = person_client.newContent(id=id, portal_type='Person')
kw = {'first_name':self.first_name1,'last_name':self.last_name1,
'description':self.description1}
person.edit(**kw)
nb_person = len(person_client.objectValues())
self.assertEqual(nb_person,59)
number_of_object = 60
for id in range(1, number_of_object+1):
person = person_client.newContent(portal_type='Person',
id=id,
first_name=self.first_name1,
last_name=self.last_name1,
description=self.description1)
if id % 10 == 0:
# each 10 objects commit and reindex
transaction.commit()
self.tic()
nb_person = len(person_client)
self.assertEquals(nb_person, number_of_object)
return nb_person
def synchronize(self, id, run=1):
def synchronize(self, id):
"""
This just define how we synchronize, we have
to define it here because it is specific to the unit testing
"""
portal_sync = self.getSynchronizationTool()
subscription = portal_sync.getSubscription(id)
subscription = portal_sync[id]
publication = None
for pub in portal_sync.getPublicationList():
if pub.getPublicationUrl()==subscription.getPublicationUrl():
for pub in portal_sync.searchFolder(portal_type='SyncML Publication',
source_reference=subscription.getDestinationReference(),
validation_state='validated'):
if pub.getUrlString() == subscription.getUrlString():
publication = pub
self.assertTrue(publication is not None)
# reset files, because we do sync by files
......@@ -227,7 +228,10 @@ class TestERP5SyncMLMixin:
file.close()
nb_message = 1
result = portal_sync.SubSync(subscription.getPath())
while result['has_response']==1:
# XXX-AUREL : in reality readResponse is called
# Why is it not call here ? This make the behaviour of
# the test rather different of what happens in real life !
while result['has_response']:
portal_sync.PubSync(publication.getPath())
if self.activity_enabled:
transaction.commit()
......@@ -237,19 +241,23 @@ class TestERP5SyncMLMixin:
transaction.commit()
self.tic()
nb_message += 1 + result['has_response']
transaction.commit()
self.tic()
return nb_message
def synchronizeWithBrokenMessage(self, id, run=1):
def synchronizeWithBrokenMessage(self, id):
"""
This just define how we synchronize, we have
to define it here because it is specific to the unit testing
"""
portal_sync = self.getSynchronizationTool()
#portal_sync.email = None # XXX To be removed
subscription = portal_sync.getSubscription(id)
subscription = portal_sync[id]
publication = None
for pub in portal_sync.getPublicationList():
if pub.getPublicationUrl()==subscription.getPublicationUrl():
for pub in portal_sync.searchFolder(portal_type='SyncML Publication',
source_reference=subscription.getDestinationReference(),
validation_state='validated'):
if pub.getUrlString() == subscription.getUrlString():
publication = pub
self.assertTrue(publication is not None)
# reset files, because we do sync by files
......@@ -264,7 +272,7 @@ class TestERP5SyncMLMixin:
file.close()
nb_message = 1
result = portal_sync.SubSync(subscription.getPath())
while result['has_response']==1:
while result['has_response']:
# We do thing three times, so that we will test
# if we manage well duplicate messages
portal_sync.PubSync(publication.getPath())
......@@ -292,34 +300,36 @@ class TestERP5SyncMLMixin:
transaction.commit()
self.tic()
nb_message += 1 + result['has_response']
transaction.commit()
self.tic()
return nb_message
def checkSynchronizationStateIsSynchronized(self, quiet=0, run=1):
def checkSynchronizationStateIsSynchronized(self):
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
for person in person_server.objectValues():
state_list = portal_sync.getSynchronizationState(person)
for state in state_list:
self.assertEqual(state[1], state[0].SYNCHRONIZED)
self.assertEquals(state[1], 'synchronized')
person_client1 = self.getPersonClient1()
for person in person_client1.objectValues():
state_list = portal_sync.getSynchronizationState(person)
for state in state_list:
self.assertEqual(state[1], state[0].SYNCHRONIZED)
self.assertEquals(state[1], 'synchronized')
person_client2 = self.getPersonClient2()
for person in person_client2.objectValues():
state_list = portal_sync.getSynchronizationState(person)
for state in state_list:
self.assertEqual(state[1], state[0].SYNCHRONIZED)
self.assertEquals(state[1], 'synchronized')
# Check for each signature that the tempXML is None
for sub in portal_sync.getSubscriptionList():
for m in sub.getSignatureList():
self.assertEquals(m.getTempXML(),None)
self.assertEquals(m.getPartialXML(),None)
for pub in portal_sync.getPublicationList():
for sub in pub.getSubscriberList():
for m in sub.getSignatureList():
self.assertEquals(m.getPartialXML(),None)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
for m in sub.contentValues():
self.assertEquals(m.getTemporaryData(), None)
self.assertEquals(m.getPartialData(), None)
for pub in portal_sync.contentValues(portal_type='SyncML Publication'):
for sub in pub.contentValues(portal_type='SyncML Subscription'):
for m in sub.contentValues():
self.assertEquals(m.getPartialData(), None)
def verifyFirstNameAndLastNameAreNotSynchronized(self, first_name,
last_name, person_server, person_client):
......@@ -328,40 +338,39 @@ class TestERP5SyncMLMixin:
"""
self.assertNotEqual(person_server.getFirstName(), first_name)
self.assertNotEqual(person_server.getLastName(), last_name)
self.assertEqual(person_client.getFirstName(), first_name)
self.assertEqual(person_client.getLastName(), last_name)
self.assertEquals(person_client.getFirstName(), first_name)
self.assertEquals(person_client.getLastName(), last_name)
def checkFirstSynchronization(self, id=None, nb_person=0):
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync.getSubscription(self.sub_id1)
subscription2 = portal_sync.getSubscription(self.sub_id2)
self.assertEqual(len(subscription1.getObjectList()), nb_person)
subscription1 = portal_sync[self.sub_id1]
subscription2 = portal_sync[self.sub_id2]
self.assertEquals(len(subscription1.getObjectList()), nb_person)
person_server = self.getPersonServer() # We also check we don't
# modify initial ob
person1_s = person_server._getOb(self.id1)
self.assertEqual(person1_s.getId(), self.id1)
self.assertEqual(person1_s.getFirstName(), self.first_name1)
self.assertEqual(person1_s.getLastName(), self.last_name1)
self.assertEquals(person1_s.getId(), self.id1)
self.assertEquals(person1_s.getFirstName(), self.first_name1)
self.assertEquals(person1_s.getLastName(), self.last_name1)
person_client1 = self.getPersonClient1()
person1_c = person_client1._getOb(id)
self.assertEqual(person1_c.getId(), id)
self.assertEqual(person1_c.getFirstName(), self.first_name1)
self.assertEqual(person1_c.getLastName(), self.last_name1)
self.assertEqual(len(subscription2.getObjectList()), nb_person)
self.assertEquals(person1_c.getId(), id)
self.assertEquals(person1_c.getFirstName(), self.first_name1)
self.assertEquals(person1_c.getLastName(), self.last_name1)
self.assertEquals(len(subscription2.getObjectList()), nb_person)
person_client2 = self.getPersonClient2()
person2_c = person_client2._getOb(id)
self.assertEqual(person2_c.getId(), id)
self.assertEqual(person2_c.getFirstName(), self.first_name1)
self.assertEqual(person2_c.getLastName(), self.last_name1)
self.assertEquals(person2_c.getId(), id)
self.assertEquals(person2_c.getFirstName(), self.first_name1)
self.assertEquals(person2_c.getLastName(), self.last_name1)
def resetSignaturePublicationAndSubscription(self):
portal_sync = self.getSynchronizationTool()
publication = portal_sync.getPublication(self.pub_id)
subscription1 = portal_sync.getSubscription(self.sub_id1)
subscription2 = portal_sync.getSubscription(self.sub_id2)
publication.resetAllSubscribers()
subscription1.resetAllSignatures()
publication = portal_sync[self.pub_id]
subscription1 = portal_sync[self.sub_id1]
publication.resetSubscriberList()
subscription1.resetSignatureList()
transaction.commit()
self.tic()
......@@ -371,27 +380,38 @@ class TestERP5SyncMLMixin:
Check the equality between two xml objects with gid as id
"""
portal_sync = self.getSynchronizationTool()
subscription = portal_sync.getSubscription(sub_id)
publication = portal_sync.getPublication(self.pub_id)
subscription = portal_sync[sub_id]
publication = portal_sync[self.pub_id]
gid_pub = publication.getGidFromObject(object_pub)
gid_sub = publication.getGidFromObject(object_sub)
self.assertEqual(gid_pub, gid_sub)
conduit = ERP5Conduit()
self.assertEquals(gid_pub, gid_sub)
conduit = getConduitByName(publication.getConduitModuleId())
xml_pub = conduit.getXMLFromObjectWithGid(object=object_pub, gid=gid_pub,
xml_mapping=publication.getXMLMapping())
xml_mapping=publication.getXmlBindingGeneratorMethodId())
#if One Way From Server there is not xml_mapping for subscription
conduit = getConduitByName(subscription.getConduitModuleId())
xml_sub = conduit.getXMLFromObjectWithGid(object=object_sub, gid=gid_sub,
xml_mapping=subscription.getXMLMapping(force))
xml_mapping=subscription.getXmlBindingGeneratorMethodId(force=force))
erp5diff = ERP5Diff()
erp5diff.compare(xml_pub, xml_sub)
result = erp5diff.outputString()
result = etree.XML(result)
identity = True
# edit workflow might not be updated, so discard workflow checking
# revision is based on workflow history, can not be checked
exclude_property_list = ('edit_workflow',)
if object_pub.getPortalType() in self.portal.getPortalDocumentTypeList():
exclude_property_list += ('revision', 'processing_status_workflow',)
for update in result:
#XXX edit workflow is not replaced, so discard workflow checking
select = update.get('select', '')
discarded_list = ('edit_workflow',)
if 'edit_workflow' in select:
new_edit_workflow_entry_xpath = 'xupdate:element/xupdate:attribute'\
'[@name = "id"][text() = "edit_workflow"]'
continue_for_excluded_property = False
for exclude_property in exclude_property_list:
if exclude_property in select:
continue_for_excluded_property = True
if continue_for_excluded_property or\
update.xpath(new_edit_workflow_entry_xpath, namespaces=update.nsmap):
continue
else:
identity = False
......@@ -401,22 +421,19 @@ class TestERP5SyncMLMixin:
object_sub.getPath(),
etree.tostring(result, pretty_print=True)))
def deletePublicationAndSubscription(self):
def deletePublicationAndSubscriptionList(self):
portal_sync = self.getSynchronizationTool()
portal_sync.manage_deletePublication(title=self.pub_id)
portal_sync.manage_deleteSubscription(title=self.sub_id1)
if portal_sync.getSubscription(title=self.sub_id2):
portal_sync.manage_deleteSubscription(title=self.sub_id2)
id_list = [id for id in portal_sync.objectIds()]
portal_sync.manage_delObjects(id_list)
class TestERP5SyncML(TestERP5SyncMLMixin):
class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
run_all_test = True
def getTitle(self):
"""
"""
return "ERP5 SyncML"
def setupPublicationAndSubscription(self, quiet=0, run=run_all_test):
def setupPublicationAndSubscription(self):
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
if person_server is not None:
......@@ -424,44 +441,44 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
portal._delObject(id='person_server')
portal._delObject(id='person_client1')
portal._delObject(id='person_client2')
self.deletePublicationAndSubscription()
self.test_02_AddPublication(quiet=1,run=1)
self.test_03_AddSubscription1(quiet=1,run=1)
self.test_04_AddSubscription2(quiet=1,run=1)
def setupPublicationAndSubscriptionAndGid(self, quiet=0, run=run_all_test):
self.setupPublicationAndSubscription(quiet=1,run=1)
self.deletePublicationAndSubscriptionList()
self.addPublication()
self.addSubscription1()
self.addSubscription2()
def setupPublicationAndSubscriptionAndGid(self):
self.setupPublicationAndSubscription()
portal_sync = self.getSynchronizationTool()
sub1 = portal_sync.getSubscription(self.sub_id1)
sub2 = portal_sync.getSubscription(self.sub_id2)
pub = portal_sync.getPublication(self.pub_id)
sub1.setConduit('ERP5ConduitTitleGid')
sub2.setConduit('ERP5ConduitTitleGid')
pub.setConduit('ERP5ConduitTitleGid')
pub.setSynchronizationIdGenerator('_generateNextId')
sub1.setSynchronizationIdGenerator('_generateNextId')
sub2.setSynchronizationIdGenerator('_generateNextId')
def checkSynchronizationStateIsConflict(self, quiet=0, run=1):
sub1 = portal_sync[self.sub_id1]
sub2 = portal_sync[self.sub_id2]
pub = portal_sync[self.pub_id]
sub1.setConduitModuleId('ERP5ConduitTitleGid')
sub2.setConduitModuleId('ERP5ConduitTitleGid')
pub.setConduitModuleId('ERP5ConduitTitleGid')
pub.setSynchronizationIdGeneratorMethodId('_generateNextId')
sub1.setSynchronizationIdGeneratorMethodId('_generateNextId')
sub2.setSynchronizationIdGeneratorMethodId('_generateNextId')
def checkSynchronizationStateIsConflict(self):
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
for person in person_server.objectValues():
if person.getId()==self.id1:
state_list = portal_sync.getSynchronizationState(person)
for state in state_list:
self.assertEqual(state[1], state[0].CONFLICT)
self.assertEquals(state[1], 'conflict')
person_client1 = self.getPersonClient1()
for person in person_client1.objectValues():
if person.getId()==self.id1:
state_list = portal_sync.getSynchronizationState(person)
for state in state_list:
self.assertEqual(state[1], state[0].CONFLICT)
self.assertEquals(state[1], 'conflict')
person_client2 = self.getPersonClient2()
for person in person_client2.objectValues():
if person.getId()==self.id1:
state_list = portal_sync.getSynchronizationState(person)
for state in state_list:
self.assertEqual(state[1], state[0].CONFLICT)
self.assertEquals(state[1], 'conflict')
# make sure sub object are also in a conflict mode
person = person_client1._getOb(self.id1)
# use a temp_object to create a no persistent object in person
......@@ -469,9 +486,9 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
person.newContent(id=self.id1, portal_type='Person', temp_object=1)
state_list = portal_sync.getSynchronizationState(sub_person)
for state in state_list:
self.assertEqual(state[1], state[0].CONFLICT)
self.assertEquals(state[1], 'conflict')
def populatePersonServerWithSubObject(self, quiet=0, run=run_all_test):
def populatePersonServerWithSubObject(self):
"""
Before this method, we need to call populatePersonServer
Then it will give the following tree :
......@@ -482,10 +499,6 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
- id1
- id2
"""
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Populate Person Server With Sub Object ')
LOG('Testing... ',0,'populatePersonServerWithSubObject')
person_server = self.getPersonServer()
person1 = person_server._getOb(self.id1)
sub_person1 = person1.newContent(id=self.id1, portal_type='Person')
......@@ -502,9 +515,9 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
sub_sub_person2.edit(**kw)
# remove ('','portal...','person_server')
len_path = len(sub_sub_person1.getPhysicalPath()) - 3
self.assertEqual(len_path, 3)
self.assertEquals(len_path, 3)
len_path = len(sub_sub_person2.getPhysicalPath()) - 3
self.assertEqual(len_path, 3)
self.assertEquals(len_path, 3)
def addAuthenticationToPublication(self, publication_id, login, password,
auth_format, auth_type):
......@@ -512,8 +525,8 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
add authentication to the publication
"""
portal_sync = self.getSynchronizationTool()
pub = portal_sync.getPublication(publication_id)
pub.setLogin(login)
pub = portal_sync[publication_id]
pub.setUserId(login)
pub.setPassword(password)
pub.setAuthenticationFormat(auth_format)
pub.setAuthenticationType(auth_type)
......@@ -525,212 +538,179 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
add authentication to the subscription
"""
portal_sync = self.getSynchronizationTool()
sub = portal_sync.getSubscription(subscription_id)
sub.setAuthenticated(False)
sub.setLogin(login)
sub = portal_sync[subscription_id]
if sub.getAuthenticationState() != 'logged_out':
sub.logout()
sub.setUserId(login)
sub.setPassword(password)
sub.setAuthenticationFormat(auth_format)
sub.setAuthenticationType(auth_type)
def test_01_HasEverything(self, quiet=0, run=run_all_test):
def test_01_HasEverything(self):
# Test if portal_synchronizations was created
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Has Everything ')
LOG('Testing... ',0,'test_01_HasEverything')
self.assertNotEqual(self.getSynchronizationTool(), None)
#self.failUnless(self.getPersonServer()!=None)
#self.failUnless(self.getPersonClient1()!=None)
#self.failUnless(self.getPersonClient2()!=None)
def test_02_AddPublication(self, quiet=0, run=run_all_test):
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Add a Publication ')
LOG('Testing... ',0,'test_02_AddPublication')
def addPublication(self):
portal_id = self.getPortalName()
portal_sync = self.getSynchronizationTool()
if portal_sync.getPublication(self.pub_id) is not None:
portal_sync.manage_deletePublication(title=self.pub_id)
portal_sync.manage_addPublication(title=self.pub_id,
publication_url=self.publication_url,
destination_path='/%s/person_server' % portal_id,
source_uri='Person',
query='objectValues',
xml_mapping=self.xml_mapping,
conduit='ERP5Conduit',
gpg_key='',
activity_enabled=self.activity_enabled,
authentication_format='b64',
authentication_type='syncml:auth-basic')
pub = portal_sync.getPublication(self.pub_id)
self.failUnless(pub is not None)
def test_03_AddSubscription1(self, quiet=0, run=run_all_test):
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Add First Subscription ')
LOG('Testing... ',0,'test_03_AddSubscription1')
if getattr(portal_sync, self.pub_id, None) is not None:
portal_sync._delObject(self.pub_id)
publication = portal_sync.newContent(
portal_type='SyncML Publication',
id=self.pub_id,
url_string=self.publication_url,
source='person_server',
source_reference='Person',
list_method_id='objectValues',
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id='ERP5Conduit',
is_activity_enabled=self.activity_enabled)
publication.validate()
transaction.commit()
self.tic()
def addSubscription1(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
if portal_sync.getSubscription(self.sub_id1) is not None:
portal_sync.manage_deleteSubscription(title=self.sub_id1)
portal_sync.manage_addSubscription(title=self.sub_id1,
publication_url=self.publication_url,
subscription_url=self.subscription_url1,
destination_path='/%s/person_client1' % portal_id,
source_uri='Person',
target_uri='Person',
query='objectValues',
xml_mapping=self.xml_mapping,
conduit='ERP5Conduit',
gpg_key='',
activity_enabled=self.activity_enabled,
login='fab',
password='myPassword')
sub = portal_sync.getSubscription(self.sub_id1)
self.failUnless(sub is not None)
def test_04_AddSubscription2(self, quiet=0, run=run_all_test):
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Add Second Subscription ')
LOG('Testing... ',0,'test_04_AddSubscription2')
if getattr(portal_sync, self.sub_id1, None) is not None:
portal_sync._delObject(self.sub_id1)
subscription = portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id1,
url_string=self.publication_url,
subscription_url_string=self.subscription_url1,
source='person_client1',
source_reference='Person',
destination_reference='Person',
list_method_id='objectValues',
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id='ERP5Conduit',
is_activity_enabled=self.activity_enabled,
syncml_alert_code='two_way',
user_id='fab',
password='myPassword')
subscription.validate()
transaction.commit()
self.tic()
def addSubscription2(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
if portal_sync.getSubscription(self.sub_id2) is not None:
portal_sync.manage_deleteSubscription(title=self.sub_id2)
portal_sync.manage_addSubscription(title=self.sub_id2,
publication_url=self.publication_url,
subscription_url=self.subscription_url2,
destination_path='/%s/person_client2' % portal_id,
source_uri='Person',
target_uri='Person',
query='objectValues',
xml_mapping=self.xml_mapping,
conduit='ERP5Conduit',
gpg_key='',
activity_enabled=self.activity_enabled,
login='fab',
password='myPassword')
sub = portal_sync.getSubscription(self.sub_id2)
self.failUnless(sub is not None)
def test_05_GetSynchronizationList(self, quiet=0, run=run_all_test):
if getattr(portal_sync, self.sub_id2, None) is not None:
portal_sync._delObject(self.sub_id2)
subscription = portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id2,
url_string=self.publication_url,
subscription_url_string=self.subscription_url2,
source='person_client2',
source_reference='Person',
destination_reference='Person',
list_method_id='objectValues',
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id='ERP5Conduit',
is_activity_enabled=self.activity_enabled,
syncml_alert_code='two_way',
user_id='fab',
password='myPassword')
subscription.validate()
transaction.commit()
self.tic()
def test_05_GetSynchronizationList(self):
# This test the getSynchronizationList, ie,
# We want to see if we retrieve both the subscription
# and the publication
if not run: return
if not quiet:
ZopeTestCase._print('\nTest getSynchronizationList ')
LOG('Testing... ',0,'test_05_GetSynchronizationList')
self.setupPublicationAndSubscription(quiet=1,run=1)
self.setupPublicationAndSubscription()
portal_sync = self.getSynchronizationTool()
synchronization_list = portal_sync.getSynchronizationList()
self.assertEqual(len(synchronization_list), self.nb_synchronization)
synchronization_list = portal_sync.objectValues()
self.assertEquals(len(synchronization_list), self.nb_synchronization)
def test_06_GetObjectList(self, quiet=0, run=run_all_test):
def test_06_GetObjectList(self):
"""
This test the default getObjectList, ie, when the
query is 'objectValues', and this also test if we enter
a new method for the query
"""
if not run: return
if not quiet:
ZopeTestCase._print('\nTest getObjectList ')
LOG('Testing... ',0,'test_06_GetObjectList')
self.login()
self.setupPublicationAndSubscription(quiet=1,run=1)
nb_person = self.populatePersonServer(quiet=1,run=1)
self.setupPublicationAndSubscription()
nb_person = self.populatePersonServer()
portal_sync = self.getSynchronizationTool()
publication_list = portal_sync.getPublicationList()
publication_list = portal_sync.contentValues(
portal_type='SyncML Publication')
publication = publication_list[0]
object_list = publication.getObjectList()
self.assertEqual(len(object_list), nb_person)
self.assertEquals(len(object_list), nb_person)
# now try to set a different method for query
def query(object):
object_list = object.objectValues()
return_list = []
for o in object_list:
if o.getId()==self.id1:
return_list.append(o)
return return_list
publication.setQuery(query)
method_id = 'PersonServer_getObjectList'
## add test cached method
py_script_params = "**kw"
py_script_body = """
return [context[%r]]
""" % self.id1
self.portal.manage_addProduct['PythonScripts'].manage_addPythonScript(
id=method_id)
py_script_obj = getattr(self.portal, method_id)
py_script_obj.ZPythonScript_edit(py_script_params, py_script_body)
publication.setListMethodId(method_id)
object_list = publication.getObjectList()
self.assertEqual(len(object_list), 1)
self.assertEquals(len(object_list), 1)
# Add the query path
portal_id = self.getPortalName()
publication.setDestinationPath('/%s/' % portal_id)
publication.setQuery('person_server/objectValues')
publication.setListMethodId('person_server/objectValues')
object_list = publication.getObjectList()
self.assertEqual(len(object_list), nb_person)
self.assertEquals(len(object_list), nb_person)
def test_07_ExportImport(self, quiet=0, run=run_all_test):
def test_07_ExportImport(self):
"""
We will try to export a person with asXML
And then try to add it to another folder with a conduit
"""
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Export and Import ')
LOG('Testing... ',0,'test_07_ExportImport')
self.login()
self.populatePersonServer(quiet=1,run=1)
self.populatePersonServer()
person_server = self.getPersonServer()
person_client1 = self.getPersonClient1()
person = person_server._getOb(self.id1)
xml_output = person.asXML()
conduit = ERP5Conduit()
conduit.addNode(object=person_client1,xml=xml_output)
self.assertEqual(len(person_client1.objectValues()), 1)
conduit.addNode(object=person_client1, xml=xml_output)
new_object = person_client1._getOb(self.id1)
self.assertEqual(new_object.getLastName(), self.last_name1)
self.assertEqual(new_object.getFirstName(), self.first_name1)
# XXX We should also looks at the workflow history
self.assertEqual(len(new_object.workflow_history[self.workflow_id]), 2)
s_local_role = person_server.get_local_roles()
c_local_role = person_client1.get_local_roles()
self.assertEqual(s_local_role,c_local_role)
def test_08_FirstSynchronization(self, quiet=0, run=run_all_test):
self.assertEquals(new_object.getLastName(), self.last_name1)
self.assertEquals(new_object.getFirstName(), self.first_name1)
self.assertEquals(person.asXML(), new_object.asXML())
def test_08_FirstSynchronization(self):
# We will try to populate the folder person_client1
# with the data form person_server
if not run: return
if not quiet:
ZopeTestCase._print('\nTest First Synchronization ')
LOG('Testing... ',0,'test_08_FirstSynchronization')
self.login()
self.setupPublicationAndSubscription(quiet=1, run=1)
nb_person = self.populatePersonServer(quiet=1, run=1)
self.setupPublicationAndSubscription()
nb_person = self.populatePersonServer()
portal_sync = self.getSynchronizationTool()
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(), SyncCode.SLOW_SYNC)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
# Synchronize the first client
nb_message1 = self.synchronize(self.sub_id1)
for sub in portal_sync.getSubscriptionList():
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
if sub.getTitle() == self.sub_id1:
self.assertEquals(sub.getSynchronizationType(), SyncCode.TWO_WAY)
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
else:
self.assertEquals(sub.getSynchronizationType(), SyncCode.SLOW_SYNC)
self.assertEqual(nb_message1, self.nb_message_first_synchronization)
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
self.assertEquals(nb_message1, self.nb_message_first_synchronization)
# Synchronize the second client
nb_message2 = self.synchronize(self.sub_id2)
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(), SyncCode.TWO_WAY)
self.assertEqual(nb_message2, self.nb_message_first_synchronization)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
self.assertEquals(nb_message2, self.nb_message_first_synchronization)
self.checkFirstSynchronization(id=self.id1, nb_person=nb_person)
def test_09_FirstSynchronizationWithLongLines(self, quiet=0, run=run_all_test):
def test_09_FirstSynchronizationWithLongLines(self):
# We will try to populate the folder person_client1
# with the data form person_server
if not run: return
if not quiet:
ZopeTestCase._print('\nTest First Synchronization With Long Lines ')
LOG('Testing... ',0,'test_09_FirstSynchronizationWithLongLines')
self.login()
self.setupPublicationAndSubscription(quiet=1,run=1)
nb_person = self.populatePersonServer(quiet=1,run=1)
self.setupPublicationAndSubscription()
nb_person = self.populatePersonServer()
person_server = self.getPersonServer()
long_line = 'a' * 10000 + ' --- '
person1_s = person_server._getOb(self.id1)
......@@ -738,61 +718,49 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
person1_s.edit(**kw)
# Synchronize the first client
nb_message1 = self.synchronize(self.sub_id1)
self.assertEqual(nb_message1, self.nb_message_first_synchronization)
self.assertEquals(nb_message1, self.nb_message_first_synchronization)
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync.getSubscription(self.sub_id1)
self.assertEqual(len(subscription1.getObjectList()), nb_person)
self.assertEqual(person1_s.getId(), self.id1)
self.assertEqual(person1_s.getFirstName(), long_line)
self.assertEqual(person1_s.getLastName(), self.last_name1)
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(len(subscription1.getObjectList()), nb_person)
self.assertEquals(person1_s.getId(), self.id1)
self.assertEquals(person1_s.getFirstName(), long_line)
self.assertEquals(person1_s.getLastName(), self.last_name1)
person_client1 = self.getPersonClient1()
person1_c = person_client1._getOb(self.id1)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
def test_10_GetObjectFromGid(self, quiet=0, run=run_all_test):
def test_10_GetObjectFromGid(self):
# We will try to get an object from a publication
# just by givin the gid
if not run: return
if not quiet:
ZopeTestCase._print('\nTest getObjectFromGid ')
LOG('Testing... ',0,'test_10_GetObjectFromGid')
self.login()
self.setupPublicationAndSubscription(quiet=1,run=1)
self.populatePersonServer(quiet=1,run=1)
self.setupPublicationAndSubscription()
self.populatePersonServer()
# By default we can just give the id
portal_sync = self.getSynchronizationTool()
publication = portal_sync.getPublication(self.pub_id)
publication = portal_sync[self.pub_id]
object = publication.getObjectFromId(self.id1)
self.failUnless(object is not None)
self.assertEqual(object.getId(), self.id1)
self.assertEquals(object.getId(), self.id1)
def test_11_GetSynchronizationState(self, quiet=0, run=run_all_test):
def test_11_GetSynchronizationState(self):
# We will try to get the state of objects
# that are just synchronized,
if not run: return
if not quiet:
ZopeTestCase._print('\nTest getSynchronizationState ')
LOG('Testing... ',0,'test_11_GetSynchronizationState')
self.test_08_FirstSynchronization(quiet=1,run=1)
# that are just synchronized
self.test_08_FirstSynchronization()
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
state_list_s = portal_sync.getSynchronizationState(person1_s)
self.assertEqual(len(state_list_s), self.nb_subscription) # one state
self.assertEquals(len(state_list_s), self.nb_subscription) # one state
# for each subscriber
person_client1 = self.getPersonClient1()
person1_c = person_client1._getOb(self.id1)
state_list_c = portal_sync.getSynchronizationState(person1_c)
self.assertEqual(len(state_list_c), 1) # one state
self.assertEquals(len(state_list_c), 1) # one state
# for each subscriber
self.checkSynchronizationStateIsSynchronized()
def test_12_UpdateSimpleData(self, quiet=0, run=run_all_test):
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Update Simple Data ')
LOG('Testing... ',0,'test_12_UpdateSimpleData')
self.test_08_FirstSynchronization(quiet=1,run=1)
def test_12_UpdateSimpleData(self):
self.test_08_FirstSynchronization()
# First we do only modification on server
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
......@@ -803,8 +771,8 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.checkSynchronizationStateIsSynchronized()
person_client1 = self.getPersonClient1()
person1_c = person_client1._getOb(self.id1)
self.assertEqual(person1_s.getFirstName(), self.first_name3)
self.assertEqual(person1_s.getLastName(), self.last_name3)
self.assertEquals(person1_s.getFirstName(), self.first_name3)
self.assertEquals(person1_s.getLastName(), self.last_name3)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
# Then we do only modification on a client
kw = {'first_name':self.first_name1,'last_name':self.last_name1}
......@@ -813,8 +781,8 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
person1_s = person_server._getOb(self.id1)
self.assertEqual(person1_s.getFirstName(), self.first_name1)
self.assertEqual(person1_s.getLastName(), self.last_name1)
self.assertEquals(person1_s.getFirstName(), self.first_name1)
self.assertEquals(person1_s.getLastName(), self.last_name1)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
# Then we do only modification on both the client and the server
# and of course, on the same object
......@@ -825,18 +793,14 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
#person1_s = person_server._getOb(self.id1)
self.assertEqual(person1_s.getFirstName(), self.first_name3)
self.assertEqual(person1_s.getDescription(), self.description3)
self.assertEquals(person1_s.getFirstName(), self.first_name3)
self.assertEquals(person1_s.getDescription(), self.description3)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
def test_13_GetConflictList(self, quiet=0, run=run_all_test):
def test_13_GetConflictList(self):
# We will try to generate a conflict and then to get it
# We will also make sure it contains what we want
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Get Conflict List ')
LOG('Testing... ',0,'test_13_GetConflictList')
self.test_08_FirstSynchronization(quiet=1,run=1)
self.test_08_FirstSynchronization()
# First we do only modification on server
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
......@@ -847,24 +811,20 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
person1_c.setDescription(self.description3)
self.synchronize(self.sub_id1)
conflict_list = portal_sync.getConflictList()
self.assertEqual(len(conflict_list), 1)
self.assertEquals(len(conflict_list), 1)
conflict = conflict_list[0]
self.assertEqual(person1_c.getDescription(), self.description3)
self.assertEqual(person1_s.getDescription(), self.description2)
self.assertEqual(conflict.getPropertyId(), 'description')
self.assertEqual(conflict.getPublisherValue(), self.description2)
self.assertEqual(conflict.getSubscriberValue(), self.description3)
self.assertEquals(person1_c.getDescription(), self.description3)
self.assertEquals(person1_s.getDescription(), self.description2)
self.assertEquals(conflict.getPropertyId(), 'description')
self.assertEquals(conflict.getLocalValue(), self.description2)
self.assertEquals(conflict.getRemoteValue(), self.description3)
subscriber = conflict.getSubscriber()
self.assertEqual(subscriber.getSubscriptionUrl(), self.subscription_url1)
self.assertEquals(subscriber.getSubscriptionUrlString(), self.subscription_url1)
def test_14_GetPublisherAndSubscriberDocument(self, quiet=0, run=run_all_test):
def test_14_GetPublisherAndSubscriberDocument(self):
# We will try to generate a conflict and then to get it
# We will also make sure it contains what we want
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Get Publisher And Subscriber Document ')
LOG('Testing... ',0,'test_14_GetPublisherAndSubscriberDocument')
self.test_13_GetConflictList(quiet=1,run=1)
self.test_13_GetConflictList()
# First we do only modification on server
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
......@@ -874,18 +834,14 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
conflict_list = portal_sync.getConflictList()
conflict = conflict_list[0]
publisher_document = conflict.getPublisherDocument()
self.assertEqual(publisher_document.getDescription(), self.description2)
self.assertEquals(publisher_document.getDescription(), self.description2)
subscriber_document = conflict.getSubscriberDocument()
self.assertEqual(subscriber_document.getDescription(), self.description3)
self.assertEquals(subscriber_document.getDescription(), self.description3)
def test_15_ApplyPublisherValue(self, quiet=0, run=run_all_test):
def test_15_ApplyPublisherValue(self):
# We will try to generate a conflict and then to get it
# We will also make sure it contains what we want
if not run: return
self.test_13_GetConflictList(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Apply Publisher Value ')
LOG('Testing... ',0,'test_15_ApplyPublisherValue')
self.test_13_GetConflictList()
portal_sync = self.getSynchronizationTool()
conflict_list = portal_sync.getConflictList()
conflict = conflict_list[0]
......@@ -896,21 +852,17 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
conflict.applyPublisherValue()
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
self.assertEqual(person1_c.getDescription(), self.description2)
self.assertEquals(person1_c.getDescription(), self.description2)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
conflict_list = portal_sync.getConflictList()
self.assertEqual(len(conflict_list), 0)
self.assertEquals(len(conflict_list), 0)
def test_16_ApplySubscriberValue(self, quiet=0, run=run_all_test):
def test_16_ApplySubscriberValue(self):
# We will try to generate a conflict and then to get it
# We will also make sure it contains what we want
if not run: return
self.test_13_GetConflictList(quiet=1,run=1)
self.test_13_GetConflictList()
portal_sync = self.getSynchronizationTool()
conflict_list = portal_sync.getConflictList()
if not quiet:
ZopeTestCase._print('\nTest Apply Subscriber Value ')
LOG('Testing... ',0,'test_16_ApplySubscriberValue')
conflict = conflict_list[0]
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
......@@ -919,23 +871,19 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
conflict.applySubscriberValue()
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
self.assertEqual(person1_s.getDescription(), self.description3)
self.assertEquals(person1_s.getDescription(), self.description3)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
conflict_list = portal_sync.getConflictList()
self.assertEqual(len(conflict_list), 0)
self.assertEquals(len(conflict_list), 0)
def test_17_AddSubObject(self, quiet=0, run=run_all_test):
def test_17_AddSubObject(self):
"""
In this test, we synchronize, then add sub object on the
server and then see if the next synchronization will also
create sub-objects on the client
"""
if not run: return
self.test_08_FirstSynchronization(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Add Sub Object ')
LOG('Testing... ',0,'test_17_AddSubObject')
self.populatePersonServerWithSubObject(quiet=1,run=1)
self.test_08_FirstSynchronization()
self.populatePersonServerWithSubObject()
self.synchronize(self.sub_id1)
self.synchronize(self.sub_id2)
self.checkSynchronizationStateIsSynchronized()
......@@ -961,7 +909,7 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
sub_sub_person_s = person_server._getOb(self.id1)._getOb(self.id1)._getOb(self.id1)
self.assertXMLViewIsEqual(self.sub_id1, sub_sub_person_s, sub_sub_person1)
def test_18_UpdateSubObject(self, quiet=0, run=run_all_test):
def test_18_UpdateSubObject(self):
"""
In this test, we start with a tree of object already
synchronized, then we update a subobject, and we will see
......@@ -969,11 +917,7 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
To make this test a bit more harder, we will update on both
the client and the server by the same time
"""
if not run: return
self.test_17_AddSubObject(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Update Sub Object ')
LOG('Testing... ',0,'test_18_UpdateSubObject')
self.test_17_AddSubObject()
person_client1 = self.getPersonClient1()
person1_c = person_client1._getOb(self.id1)
sub_person1_c = person1_c._getOb(self.id1)
......@@ -986,24 +930,20 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
sub_sub_person_s.edit(**kw)
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
# refresh objects after synchronisation
# refresh objects after synchronization
sub_sub_person_c = sub_person1_c._getOb(self.id2)
sub_sub_person_s = person_server._getOb(self.id1)._getOb(self.id1)._getOb(self.id2)
#self.assertEqual(sub_sub_person_c.getDescription(), self.description3)
#self.assertEqual(sub_sub_person_c.getFirstName(), self.first_name3)
#self.assertEquals(sub_sub_person_c.getDescription(), self.description3)
#self.assertEquals(sub_sub_person_c.getFirstName(), self.first_name3)
self.assertXMLViewIsEqual(self.sub_id1, sub_sub_person_s, sub_sub_person_c)
def test_19_DeleteObject(self, quiet=0, run=run_all_test):
def test_19_DeleteObject(self):
"""
We will do a first synchronization, then delete an object on both
sides, and we will see if nothing is left on the server and also
on the two clients
"""
if not run: return
self.test_08_FirstSynchronization(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Delete Object ')
LOG('Testing... ',0,'test_19_DeleteObject')
self.test_08_FirstSynchronization()
person_server = self.getPersonServer()
person_server.manage_delObjects(self.id1)
person_client1 = self.getPersonClient1()
......@@ -1012,14 +952,14 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.synchronize(self.sub_id2)
self.checkSynchronizationStateIsSynchronized()
portal_sync = self.getSynchronizationTool()
publication = portal_sync.getPublication(self.pub_id)
subscription1 = portal_sync.getSubscription(self.sub_id1)
subscription2 = portal_sync.getSubscription(self.sub_id2)
self.assertEqual(len(publication.getObjectList()), 0)
self.assertEqual(len(subscription1.getObjectList()), 0)
self.assertEqual(len(subscription2.getObjectList()), 0)
def test_20_DeleteSubObject(self, quiet=0, run=run_all_test):
publication = portal_sync[self.pub_id]
subscription1 = portal_sync[self.sub_id1]
subscription2 = portal_sync[self.sub_id2]
self.assertEquals(len(publication.getObjectList()), 0)
self.assertEquals(len(subscription1.getObjectList()), 0)
self.assertEquals(len(subscription2.getObjectList()), 0)
def test_20_DeleteSubObject(self):
"""
We will do a first synchronization, then delete a sub-object on both
sides, and we will see if nothing is left on the server and also
......@@ -1031,11 +971,7 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
- id1
- id2
"""
if not run: return
self.test_17_AddSubObject(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Delete Sub Object ')
LOG('Testing... ',0,'test_20_DeleteSubObject')
self.test_17_AddSubObject()
person_server = self.getPersonServer()
sub_object_s = person_server._getOb(self.id1)._getOb(self.id1)
sub_object_s.manage_delObjects(self.id1)
......@@ -1048,23 +984,19 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.synchronize(self.sub_id2)
self.checkSynchronizationStateIsSynchronized()
# refresh objects
sub_object_c1 = person_client1._getOb(self.id1)
sub_object_c2 = person_client2._getOb(self.id1)
len_s = len(sub_object_s.objectValues(portal_type='Person'))
len_c1 = len(sub_object_c1.objectValues(portal_type='Person'))
len_c2 = len(sub_object_c2.objectValues(portal_type='Person'))
self.failUnless(len_s==len_c1==len_c2==0)
def test_21_GetConflictListOnSubObject(self, quiet=0, run=run_all_test):
sub_object_c1 = person_client1._getOb(self.id1)._getOb(self.id1)
sub_object_c2 = person_client2._getOb(self.id1)._getOb(self.id1)
len_s = len(sub_object_s.objectValues())
len_c1 = len(sub_object_c1.objectValues())
len_c2 = len(sub_object_c2.objectValues())
self.assertTrue(len_s==len_c1==len_c2==0)
def test_21_GetConflictListOnSubObject(self):
"""
We will change several attributes on a sub object on both the server
and a client, then we will see if we have correctly the conflict list
"""
if not run: return
self.test_17_AddSubObject(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Get Conflict List On Sub Object ')
LOG('Testing... ',0,'test_21_GetConflictListOnSubObject')
self.test_17_AddSubObject()
person_server = self.getPersonServer()
object_s = person_server._getOb(self.id1)
sub_object_s = object_s._getOb(self.id1)
......@@ -1080,24 +1012,20 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.synchronize(self.sub_id1)
portal_sync = self.getSynchronizationTool()
conflict_list = portal_sync.getConflictList()
self.assertEqual(len(conflict_list), 2)
self.assertEquals(len(conflict_list), 2)
conflict_list = portal_sync.getConflictList(sub_object_c1)
self.assertEqual(len(conflict_list), 0)
self.assertEquals(len(conflict_list), 0)
conflict_list = portal_sync.getConflictList(object_s)
self.assertEqual(len(conflict_list), 0)
self.assertEquals(len(conflict_list), 0)
conflict_list = portal_sync.getConflictList(sub_object_s)
self.assertEqual(len(conflict_list), 2)
self.assertEquals(len(conflict_list), 2)
def test_22_ApplyPublisherDocumentOnSubObject(self, quiet=0, run=run_all_test):
def test_22_ApplyPublisherDocumentOnSubObject(self):
"""
there's several conflict on a sub object, we will see if we can
correctly have the publisher version of this document
"""
if not run: return
self.test_21_GetConflictListOnSubObject(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Apply Publisher Document On Sub Object ')
LOG('Testing... ',0,'test_22_ApplyPublisherDocumentOnSubObject')
self.test_21_GetConflictListOnSubObject()
portal_sync = self.getSynchronizationTool()
conflict_list = portal_sync.getConflictList()
conflict = conflict_list[0]
......@@ -1111,21 +1039,17 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.synchronize(self.sub_id1)
self.synchronize(self.sub_id2)
self.checkSynchronizationStateIsSynchronized()
self.assertEqual(sub_object_s.getDescription(), self.description2)
self.assertEqual(sub_object_s.getLanguage(), self.lang2)
self.assertEquals(sub_object_s.getDescription(), self.description2)
self.assertEquals(sub_object_s.getLanguage(), self.lang2)
self.assertXMLViewIsEqual(self.sub_id1, sub_object_s, sub_object_c1)
self.assertXMLViewIsEqual(self.sub_id2, sub_object_s, sub_object_c2)
def test_23_ApplySubscriberDocumentOnSubObject(self, quiet=0, run=run_all_test):
def test_23_ApplySubscriberDocumentOnSubObject(self):
"""
there's several conflict on a sub object, we will see if we can
correctly have the subscriber version of this document
"""
if not run: return
self.test_21_GetConflictListOnSubObject(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Apply Subscriber Document On Sub Object ')
LOG('Testing... ',0,'test_23_ApplySubscriberDocumentOnSubObject')
self.test_21_GetConflictListOnSubObject()
portal_sync = self.getSynchronizationTool()
conflict_list = portal_sync.getConflictList()
conflict = conflict_list[0]
......@@ -1143,32 +1067,28 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
sub_object_s = person_server._getOb(self.id1)._getOb(self.id1)
sub_object_c1 = person_client1._getOb(self.id1)._getOb(self.id1)
sub_object_c2 = person_client2._getOb(self.id1)._getOb(self.id1)
#self.assertEqual(sub_object_s.getDescription(), self.description3)
#self.assertEqual(sub_object_s.getLanguage(), self.lang3)
#self.assertEquals(sub_object_s.getDescription(), self.description3)
#self.assertEquals(sub_object_s.getLanguage(), self.lang3)
self.assertXMLViewIsEqual(self.sub_id1, sub_object_s, sub_object_c1)
self.assertXMLViewIsEqual(self.sub_id2, sub_object_s, sub_object_c2)
def test_24_SynchronizeWithStrangeGid(self, quiet=0, run=run_all_test):
def test_24_SynchronizeWithStrangeGid(self):
"""
By default, the synchronization process use the id in order to
recognize objects (because by default, getGid==getId. Here, we will see
if it also works with a somewhat strange getGid
"""
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Synchronize With Strange Gid ')
LOG('Testing... ',0,'test_24_SynchronizeWithStrangeGid')
self.login()
self.setupPublicationAndSubscriptionAndGid(quiet=1,run=1)
nb_person = self.populatePersonServer(quiet=1,run=1)
self.setupPublicationAndSubscriptionAndGid()
nb_person = self.populatePersonServer()
# This will test adding object
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync.getSubscription(self.sub_id1)
self.assertEqual(len(subscription1.getObjectList()), nb_person)
publication = portal_sync.getPublication(self.pub_id)
self.assertEqual(len(publication.getObjectList()), nb_person)
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(len(subscription1.getObjectList()), nb_person)
publication = portal_sync[self.pub_id]
self.assertEquals(len(publication.getObjectList()), nb_person)
gid = self.first_name1 + ' ' + self.last_name1 # ie the title 'Sebastien Robin'
gid = b16encode(gid)
person_c1 = subscription1.getObjectFromGid(gid)
......@@ -1176,13 +1096,13 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.failUnless(id_c1 in ('1','2')) # id given by the default generateNewId
person_s = publication.getSubscriber(self.subscription_url1).getObjectFromGid(gid)
id_s = person_s.getId()
self.assertEqual(id_s, self.id1)
self.assertEquals(id_s, self.id1)
# This will test updating object
person_s.setDescription(self.description3)
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
self.assertEqual(person_s.getDescription(), self.description3)
self.assertEqual(person_c1.getDescription(), self.description3)
self.assertEquals(person_s.getDescription(), self.description3)
self.assertEquals(person_c1.getDescription(), self.description3)
self.assertXMLViewIsEqual(self.sub_id1, person_s, person_c1)
# This will test deleting object
person_server = self.getPersonServer()
......@@ -1190,23 +1110,19 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
person_server.manage_delObjects(self.id2)
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
self.assertEqual(len(subscription1.getObjectList()), (nb_person-1))
self.assertEqual(len(publication.getObjectList()), (nb_person-1))
self.assertEquals(len(subscription1.getObjectList()), (nb_person-1))
self.assertEquals(len(publication.getObjectList()), (nb_person-1))
person_s = publication.getSubscriber(self.subscription_url1).getObjectFromGid(gid)
person_c1 = subscription1.getObjectFromGid(gid)
self.assertEqual(person_s.getDescription(), self.description3)
self.assertEquals(person_s.getDescription(), self.description3)
self.assertXMLViewIsEqual(self.sub_id1, person_s, person_c1)
def test_25_MultiNodeConflict(self, quiet=0, run=run_all_test):
def test_25_MultiNodeConflict(self):
"""
We will create conflicts with 3 differents nodes, and we will
solve it by taking one full version of documents.
"""
if not run: return
self.test_08_FirstSynchronization(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Multi Node Conflict ')
LOG('Testing... ',0,'test_25_MultiNodeConflict')
self.test_08_FirstSynchronization()
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
......@@ -1226,24 +1142,24 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.synchronize(self.sub_id1)
self.synchronize(self.sub_id2)
conflict_list = portal_sync.getConflictList()
self.assertEqual(len(conflict_list), 6)
self.assertEquals(len(conflict_list), 6)
# check if we have the state conflict on all clients
self.checkSynchronizationStateIsConflict()
# we will take :
# description on person_server
# language on person_client1
# format on person_client2
for conflict in conflict_list :
subscriber = conflict.getSubscriber()
property = conflict.getPropertyId()
resolve = 0
if property == 'language':
if subscriber.getSubscriptionUrl()==self.subscription_url1:
if subscriber.getSubscriptionUrlString() == self.subscription_url1:
resolve = 1
conflict.applySubscriberValue()
if property == 'format':
if subscriber.getSubscriptionUrl()==self.subscription_url2:
if subscriber.getSubscriptionUrlString() == self.subscription_url2:
resolve = 1
conflict.applySubscriberValue()
if not resolve:
......@@ -1251,12 +1167,12 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.synchronize(self.sub_id1)
self.synchronize(self.sub_id2)
self.checkSynchronizationStateIsSynchronized()
self.assertEqual(person1_c1.getDescription(), self.description2)
self.assertEqual(person1_c1.getLanguage(), self.lang3)
self.assertEqual(person1_c1.getFormat(), self.format4)
self.assertEqual(person1_s.getDescription(), self.description2)
self.assertEqual(person1_s.getLanguage(), self.lang3)
self.assertEqual(person1_s.getFormat(), self.format4)
self.assertEquals(person1_c1.getDescription(), self.description2)
self.assertEquals(person1_c1.getLanguage(), self.lang3)
self.assertEquals(person1_c1.getFormat(), self.format4)
self.assertEquals(person1_s.getDescription(), self.description2)
self.assertEquals(person1_s.getLanguage(), self.lang3)
self.assertEquals(person1_s.getFormat(), self.format4)
self.assertXMLViewIsEqual(self.sub_id2, person1_s, person1_c2)
# the workflow has one more "edit_workflow" in person1_c1
self.synchronize(self.sub_id1)
......@@ -1264,47 +1180,12 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.assertXMLViewIsEqual(self.sub_id2, person1_s, person1_c2)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c1)
def test_26_SynchronizeWorkflowHistory(self, quiet=0, run=run_all_test):
"""
We will do a synchronization, then we will edit two times
the object on the server, then two times the object on the
client, and see if the global history as 4 more actions.
"""
if not run: return
self.test_08_FirstSynchronization(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Synchronize WorkflowHistory ')
LOG('Testing... ',0,'test_26_SynchronizeWorkflowHistory')
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
person_client1 = self.getPersonClient1()
person1_c = person_client1._getOb(self.id1)
kw1 = {'description':self.description1}
kw2 = {'description':self.description2}
len_wf = len(person1_s.workflow_history[self.workflow_id])
person1_s.edit(**kw2)
person1_c.edit(**kw2)
person1_s.edit(**kw1)
person1_c.edit(**kw1)
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
# refresh documents
person1_s = person_server._getOb(self.id1)
person1_c = person_client1._getOb(self.id1)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
self.assertEqual(len(person1_s.workflow_history[self.workflow_id]), len_wf+4)
self.assertEqual(len(person1_c.workflow_history[self.workflow_id]), len_wf+4)
def test_27_UpdateLocalRole(self, quiet=0, run=run_all_test):
def test_27_UpdateLocalRole(self):
"""
We will do a first synchronization, then modify, add and delete
an user role and see if it is correctly synchronized
"""
if not run: return
self.test_08_FirstSynchronization(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Update Local Role ')
LOG('Testing... ',0,'test_27_UpdateLocalRole')
self.test_08_FirstSynchronization()
# First, Create a new user
uf = self.getPortal().acl_users
uf._doAddUser('jp', '', ['Manager'], [])
......@@ -1330,24 +1211,20 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
role_2_s = person2_s.get_local_roles()
role_1_c = person1_c.get_local_roles()
role_2_c = person2_c.get_local_roles()
self.assertEqual(role_1_s,role_1_c)
self.assertEqual(role_2_s,role_2_c)
self.assertEquals(role_1_s,role_1_c)
self.assertEquals(role_2_s,role_2_c)
def test_28_PartialData(self, quiet=0, run=run_all_test):
def test_28_PartialData(self):
"""
We will do a first synchronization, then we will do a change, then
we will modify the SyncCode max_line value so it
it will generate many messages
"""
if not run: return
self.test_08_FirstSynchronization(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Partial Data ')
LOG('Testing... ',0,'test_28_PartialData')
previous_max_lines = SyncCode.MAX_LINES
self.test_08_FirstSynchronization()
previous_max_lines = MAX_LEN
try:
SyncCode.MAX_LINES = 10
self.populatePersonServerWithSubObject(quiet=1,run=1)
SynchronizationTool.MAX_LEN = 1 << 8
self.populatePersonServerWithSubObject()
self.synchronize(self.sub_id1)
self.synchronize(self.sub_id2)
self.checkSynchronizationStateIsSynchronized()
......@@ -1358,9 +1235,9 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
sub_sub_person2 = sub_person1_c._getOb(self.id2)
# remove ('','portal...','person_server')
len_path = len(sub_sub_person1.getPhysicalPath()) - 3
self.assertEqual(len_path, 3)
self.assertEquals(len_path, 3)
len_path = len(sub_sub_person2.getPhysicalPath()) - 3
self.assertEqual(len_path, 3)
self.assertEquals(len_path, 3)
self.assertEquals(sub_sub_person1.getDescription(),self.description1)
self.assertEquals(sub_sub_person1.getFirstName(),self.first_name1)
self.assertEquals(sub_sub_person1.getLastName(),self.last_name1)
......@@ -1368,56 +1245,49 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.assertEquals(sub_sub_person2.getFirstName(),self.first_name2)
self.assertEquals(sub_sub_person2.getLastName(),self.last_name2)
finally:
SyncCode.MAX_LINES = previous_max_lines
SynchronizationTool.MAX_LEN = previous_max_lines
def test_29_BrokenMessage(self, quiet=0, run=run_all_test):
def test_29_BrokenMessage(self):
"""
With http synchronization, when a message is not well
received, then we send message again, we want to
be sure that is such case we don't do stupid things
If we want to make this test more intersting, it is
better to split messages
"""
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Broken Message ')
LOG('Testing... ',0,'test_29_BrokenMessage')
previous_max_lines = SyncCode.MAX_LINES
SyncCode.MAX_LINES = 10
self.setupPublicationAndSubscription(quiet=1,run=1)
nb_person = self.populatePersonServer(quiet=1,run=1)
previous_max_lines = SyncMLConstant.MAX_LEN
SyncMLConstant.MAX_LEN = 1 << 4
self.setupPublicationAndSubscription()
nb_person = self.populatePersonServer()
# Synchronize the first client
nb_message1 = self.synchronizeWithBrokenMessage(self.sub_id1)
#self.failUnless(nb_message1==self.nb_message_first_synchronization)
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync.getSubscription(self.sub_id1)
self.assertEqual(len(subscription1.getObjectList()), nb_person)
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(len(subscription1.getObjectList()), nb_person)
person_server = self.getPersonServer() # We also check we don't
# modify initial ob
person1_s = person_server._getOb(self.id1)
person_client1 = self.getPersonClient1()
person1_c = person_client1._getOb(self.id1)
self.assertEqual(person1_s.getId(), self.id1)
self.assertEqual(person1_s.getFirstName(), self.first_name1)
self.assertEqual(person1_s.getLastName(), self.last_name1)
self.assertEquals(person1_s.getId(), self.id1)
self.assertEquals(person1_s.getFirstName(), self.first_name1)
self.assertEquals(person1_s.getLastName(), self.last_name1)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
SyncCode.MAX_LINES = previous_max_lines
SyncMLConstant.MAX_LEN = previous_max_lines
def test_30_GetSynchronizationType(self, quiet=0, run=run_all_test):
def test_30_GetSynchronizationType(self):
# We will try to update some simple data, first
# we change on the server side, then on the client side
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Get Synchronization Type ')
LOG('Testing... ',0,'test_30_GetSynchronizationType')
self.test_08_FirstSynchronization(quiet=1,run=1)
self.test_08_FirstSynchronization()
# First we do only modification on server
# Check for each subsription that the synchronization type
# is TWO WAY
portal_sync = self.getSynchronizationTool()
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(),SyncCode.TWO_WAY)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
kw = {'first_name':self.first_name3,'last_name':self.last_name3}
......@@ -1431,8 +1301,8 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
person1_c.edit(**kw)
self.synchronize(self.sub_id1)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(),SyncCode.TWO_WAY)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
# Then we do only modification on both the client and the server
# and of course, on the same object
kw = {'first_name':self.first_name3}
......@@ -1441,19 +1311,15 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
person1_c.edit(**kw)
self.synchronize(self.sub_id1)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(),SyncCode.TWO_WAY)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
def test_31_UpdateLocalPermission(self, quiet=0, run=run_all_test):
def test_31_UpdateLocalPermission(self):
"""
We will do a first synchronization, then modify, add and delete
an user role and see if it is correctly synchronized
"""
if not run: return
self.test_08_FirstSynchronization(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Update Local Permission ')
LOG('Testing... ',0,'test_31_UpdateLocalPermission')
self.test_08_FirstSynchronization()
# then create roles
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
......@@ -1477,13 +1343,13 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
role_2_s = person2_s.get_local_permissions()
role_1_c = person1_c.get_local_permissions()
role_2_c = person2_c.get_local_permissions()
self.assertEqual(role_1_s,role_1_c)
self.assertEqual(role_2_s,role_2_c)
self.assertEquals(role_1_s, role_1_c)
self.assertEquals(role_2_s, role_2_c)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
self.assertXMLViewIsEqual(self.sub_id2, person2_s, person2_c)
person1_s.manage_setLocalPermissions('View',['Owner'])
person2_s.manage_setLocalPermissions('View',None)
person2_s.manage_setLocalPermissions('View management screens',())
person1_s.manage_setLocalPermissions('View', ['Owner'])
person2_s.manage_setLocalPermissions('View', None)
person2_s.manage_setLocalPermissions('View management screens', ())
self.synchronize(self.sub_id1)
self.synchronize(self.sub_id2)
......@@ -1502,72 +1368,71 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
self.assertXMLViewIsEqual(self.sub_id2, person2_s, person2_c)
def test_32_AddOneWaySubscription(self, quiet=0, run=run_all_test):
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Add One Way Subscription ')
LOG('Testing... ',0,'test_32_AddOneWaySubscription')
def addOneWayFromServerSubscription(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
if portal_sync.getSubscription(self.sub_id1) is not None:
portal_sync.manage_deleteSubscription(title=self.sub_id1)
portal_sync.manage_addSubscription(title=self.sub_id1,
publication_url=self.publication_url,
subscription_url=self.subscription_url1,
destination_path='/%s/person_client1' % portal_id,
source_uri='Person',
target_uri='Person',
query='objectValues',
xml_mapping=self.xml_mapping,
conduit='ERP5Conduit',
gpg_key='',
activity_enabled=self.activity_enabled,
alert_code = SyncCode.ONE_WAY_FROM_SERVER,
login = 'fab',
password = 'myPassword')
sub = portal_sync.getSubscription(self.sub_id1)
self.assertTrue(sub.isOneWayFromServer())
self.failUnless(sub is not None)
def test_33_OneWaySync(self, quiet=0, run=run_all_test):
syncml_alert_code = self.portal.portal_categories.syncml_alert_code.\
one_way_from_server
subscription = portal_sync._getOb(self.sub_id1, None)
if subscription is not None:
subscription.edit(syncml_alert_code_value=syncml_alert_code)
else:
subscription = portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id1,
url_string=self.publication_url,
subscription_url_string=self.subscription_url1,
source='person_client1',
source_reference='Person',
destination_reference='Person',
list_method_id='objectValues',
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id='ERP5Conduit',
is_activity_enabled=self.activity_enabled,
syncml_alert_code_value=syncml_alert_code,
user_id='fab',
password='myPassword')
transaction.commit()
self.tic()
self.assertTrue(subscription.isOneWayFromServer())
def test_33_OneWayFromServer(self):
"""
We will test if we can synchronize only from to server to the client.
We want to make sure in this case that all modifications on the client
will not be taken into account.
"""
if not run: return
if not quiet:
ZopeTestCase._print('\nTest One Way Sync ')
LOG('Testing... ',0,'test_33_OneWaySync')
person_server = self.getPersonServer()
if person_server is not None:
portal = self.getPortal()
portal._delObject(id='person_server')
portal._delObject(id='person_client1')
portal._delObject(id='person_client2')
self.deletePublicationAndSubscription()
self.test_02_AddPublication(quiet=1,run=1)
self.test_32_AddOneWaySubscription(quiet=1,run=1)
self.deletePublicationAndSubscriptionList()
self.addPublication()
self.addOneWayFromServerSubscription()
nb_person = self.populatePersonServer(quiet=1,run=1)
nb_person = self.populatePersonServer()
portal_sync = self.getSynchronizationTool()
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(), SyncCode.SLOW_SYNC)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'one_way_from_server')
# First do the sync from the server to the client
nb_message1 = self.synchronize(self.sub_id1)
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(), SyncCode.ONE_WAY_FROM_SERVER)
self.assertEquals(nb_message1,self.nb_message_first_synchronization)
subscription1 = portal_sync.getSubscription(self.sub_id1)
self.assertEquals(len(subscription1.getObjectList()),nb_person)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'one_way_from_server')
self.assertEquals(nb_message1, self.nb_message_first_synchronization)
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(len(subscription1.getObjectList()), nb_person)
person_server = self.getPersonServer() # We also check we don't
# modify initial ob
person1_s = person_server._getOb(self.id1)
person_client1 = self.getPersonClient1()
person1_c = person_client1._getOb(self.id1)
self.assertEqual(person1_s.getId(), self.id1)
self.assertEqual(person1_s.getFirstName(), self.first_name1)
self.assertEqual(person1_s.getLastName(), self.last_name1)
self.assertEquals(person1_s.getId(), self.id1)
self.assertEquals(person1_s.getFirstName(), self.first_name1)
self.assertEquals(person1_s.getLastName(), self.last_name1)
self.assertEquals(person1_c.getLastName(), self.last_name1)
self.checkSynchronizationStateIsSynchronized()
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c, force=1)
# Then we change things on both sides and we look if there
......@@ -1575,12 +1440,12 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
person1_c.setFirstName(self.first_name2)
person1_s.setLastName(self.last_name2)
nb_message1 = self.synchronize(self.sub_id1)
#In One_From_Server Sync not modify the first_name in client because any
#datas client sent
# In One_From_Server Sync mode, first_name of object on client side
# doesn't change because no data is send from Subscription
self.assertEquals(person1_c.getFirstName(), self.first_name2)
self.assertEquals(person1_c.getLastName(), self.last_name2)
self.assertEquals(person1_s.getFirstName(), self.first_name1)
self.assertEquals(person1_s.getLastName(), self.last_name2)
self.assertEquals(person1_s.getLastName(), self.last_name2)
#reset for refresh sync
#after synchronize, the client object retrieve value of server
self.resetSignaturePublicationAndSubscription()
......@@ -1595,7 +1460,109 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.checkSynchronizationStateIsSynchronized()
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c, force=1)
def test_34_encoding(self, quiet=0, run=run_all_test):
def addOneWayFormClientSubscription(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
syncml_alert_code = self.portal.portal_categories.syncml_alert_code.\
one_way_from_client
subscription = portal_sync._getOb(self.sub_id1, None)
if subscription is not None:
subscription.edit(syncml_alert_code_value=syncml_alert_code)
else:
subscription = portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id1,
url_string=self.publication_url,
subscription_url_string=self.subscription_url1,
source='person_client1',
source_reference='Person',
destination_reference='Person',
list_method_id='objectValues',
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id='ERP5Conduit',
is_activity_enabled=self.activity_enabled,
syncml_alert_code_value=syncml_alert_code,
user_id='fab',
password='myPassword')
transaction.commit()
self.tic()
def test_OneWayFromClient(self):
"""
Check one way from client configuration
"""
person_server = self.getPersonServer()
if person_server is not None:
portal = self.getPortal()
portal._delObject(id='person_server')
portal._delObject(id='person_client1')
portal._delObject(id='person_client2')
self.deletePublicationAndSubscriptionList()
self.addPublication()
self.addOneWayFormClientSubscription()
nb_person = self.populatePersonClient1()
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(subscription1.getSyncmlAlertCode(), 'one_way_from_client')
# First do the sync from the server to the client
nb_message1 = self.synchronize(self.sub_id1)
self.assertEquals(subscription1.getSyncmlAlertCode(),
'one_way_from_client')
#self.assertEquals(nb_message1, self.nb_message_first_synchronization)
self.assertEquals(len(subscription1), nb_person)
client_person_module = self.getPersonClient1() # We also check we don't
# modify initial ob
object_id = '1'
client_person = client_person_module._getOb(object_id)
server_person_module = self.getPersonServer()
server_person = server_person_module._getOb(object_id)
self.assertEquals(client_person.getId(), object_id)
self.assertEquals(client_person.getFirstName(), self.first_name1)
self.assertEquals(client_person.getLastName(), self.last_name1)
self.checkSynchronizationStateIsSynchronized()
self.assertXMLViewIsEqual(self.sub_id1, client_person, server_person,
force=True)
# Then we change things on both sides and we look if there
# is synchronization only from only one client
# This will create a conflict !!
client_person.setFirstName(self.first_name2)
server_person.setLastName(self.last_name2)
nb_message1 = self.synchronize(self.sub_id1)
# refresh documents
server_person = server_person_module._getOb(object_id)
client_person = client_person_module._getOb(object_id)
# In One_From_Client Sync mode, first_name of object on server side
# doesn't change because no data is send from Subscription
self.assertEquals(server_person.getFirstName(), self.first_name2)
self.assertEquals(server_person.getLastName(), self.last_name2)
self.assertEquals(client_person.getFirstName(), self.first_name2)
self.assertEquals(client_person.getLastName(), self.last_name1)
## Check again after conflict resolution
#self.assertEquals(server_person.getFirstName(), self.first_name2)
#self.assertEquals(server_person.getLastName(), self.last_name2)
#self.assertEquals(client_person.getFirstName(), self.first_name2)
#self.assertEquals(client_person.getLastName(), self.last_name1)
# reset for refresh sync
# after synchronize, the client object retrieve value of server
self.resetSignaturePublicationAndSubscription()
nb_message1 = self.synchronize(self.sub_id1)
# refresh documents
server_person = server_person_module._getOb(object_id)
client_person = client_person_module._getOb(object_id)
self.assertEquals(server_person.getFirstName(), self.first_name2)
self.assertEquals(server_person.getLastName(), self.last_name1)
self.checkSynchronizationStateIsSynchronized()
self.assertXMLViewIsEqual(self.sub_id1, client_person, server_person,
force=True)
def test_34_encoding(self):
"""
We will test if we can encode strings with b64encode to encode
the login and password for authenticated sessions
......@@ -1603,12 +1570,7 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase):
#when there will be other format implemented with encode method,
#there will be tested here
if not run: return
self.test_08_FirstSynchronization(quiet=1,run=1)
if not quiet:
ZopeTestCase._print('\nTest Strings Encoding ')
LOG('Testing... ',0,'test_34_encoding')
self.test_08_FirstSynchronization()
#define some strings :
python = 'www.python.org'
awaited_result_python = "d3d3LnB5dGhvbi5vcmc="
......@@ -1623,43 +1585,43 @@ sdf\xc3\xa7\xc3\xa7\xc3\xa7_df___&&\xc3\xa9]]]\xc2\xb0\xc2\xb0\xc2\
SElKS0xNTk9QUVJTVFVWV1hZWsOpw6jDp8OgQF5+wrUmwrIwMTIzNDU2Nzg5IUAjMF4mKigpOzo8Pi\
wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
#test just b64encode
self.assertEqual(b64encode(python), awaited_result_python)
self.assertEqual(b64encode(""), "")
self.assertEqual(b64encode(long_string), awaited_result_long_string)
self.assertEqual(b64decode(awaited_result_python), python)
self.assertEqual(b64decode(""), "")
self.assertEqual(b64decode(awaited_result_long_string), long_string)
self.assertEquals(b64encode(python), awaited_result_python)
self.assertEquals(b64encode(""), "")
self.assertEquals(b64encode(long_string), awaited_result_long_string)
self.assertEquals(b64decode(awaited_result_python), python)
self.assertEquals(b64decode(""), "")
self.assertEquals(b64decode(awaited_result_long_string), long_string)
# test with the ERP5 functions
portal_sync = self.getSynchronizationTool()
publication = portal_sync.getPublication(self.pub_id)
subscription1 = portal_sync.getSubscription(self.sub_id1)
string_encoded = subscription1.encode('b64', python)
self.assertEqual(string_encoded, awaited_result_python)
string_decoded = subscription1.decode('b64', awaited_result_python)
self.assertEqual(string_decoded, python)
self.failUnless(subscription1.isDecodeEncodeTheSame(string_encoded,
python, 'b64'))
self.failUnless(subscription1.isDecodeEncodeTheSame(string_encoded,
string_decoded, 'b64'))
string_encoded = subscription1.encode('b64', long_string)
self.assertEqual(string_encoded, awaited_result_long_string)
string_decoded = subscription1.decode('b64', awaited_result_long_string)
self.assertEqual(string_decoded, long_string)
self.failUnless(subscription1.isDecodeEncodeTheSame(string_encoded,
long_string, 'b64'))
self.failUnless(subscription1.isDecodeEncodeTheSame(string_encoded,
string_decoded, 'b64'))
self.assertEqual(subscription1.encode('b64', ''), '')
self.assertEqual(subscription1.decode('b64', ''), '')
self.failUnless(subscription1.isDecodeEncodeTheSame(
subscription1.encode('b64', ''), '', 'b64'))
def test_35_authentication(self, quiet=0, run=run_all_test):
publication = portal_sync[self.pub_id]
subscription1 = portal_sync[self.sub_id1]
string_encoded = encode('b64', python)
self.assertEquals(string_encoded, awaited_result_python)
string_decoded = decode('b64', awaited_result_python)
self.assertEquals(string_decoded, python)
self.failUnless(isDecodeEncodeTheSame(string_encoded,
python, 'b64'))
self.failUnless(isDecodeEncodeTheSame(string_encoded,
string_decoded, 'b64'))
string_encoded = encode('b64', long_string)
self.assertEquals(string_encoded, awaited_result_long_string)
string_decoded = decode('b64', awaited_result_long_string)
self.assertEquals(string_decoded, long_string)
self.failUnless(isDecodeEncodeTheSame(string_encoded,
long_string, 'b64'))
self.failUnless(isDecodeEncodeTheSame(string_encoded,
string_decoded, 'b64'))
self.assertEquals(encode('b64', ''), '')
self.assertEquals(decode('b64', ''), '')
self.failUnless(isDecodeEncodeTheSame(
encode('b64', ''), '', 'b64'))
def test_35_authentication(self):
"""
we will test
- if we can't synchronize without good authentication for an
......@@ -1667,13 +1629,7 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
- if we can synchronize without of with (and bad or good) authentication
for an not required autentication publication
"""
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Authentication ')
LOG('Testing... ',0,'test_35_authentication')
self.test_08_FirstSynchronization(quiet=1,run=1)
self.test_08_FirstSynchronization()
# First we do only modification on client
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
......@@ -1683,7 +1639,7 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
kw = {'first_name':self.first_name3,'last_name':self.last_name3}
person1_c.edit(**kw)
#check that it's not synchronize
self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name3,
self.last_name3, person1_s, person1_c)
......@@ -1693,7 +1649,7 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
self.assertEquals(person1_s.getFirstName(), self.first_name3)
self.assertEquals(person1_s.getLastName(), self.last_name3)
#adding authentication :
self.addAuthenticationToPublication(self.pub_id, 'fab', 'myPassword', 'b64',
'syncml:auth-basic')
......@@ -1739,7 +1695,7 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
self.synchronize(self.sub_id1)
self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name1,
self.last_name1, person1_s, person1_c)
#with the good password
self.addAuthenticationToSubscription(self.sub_id1, 'fab', 'myPassword',
'b64', 'syncml:auth-basic')
......@@ -1779,29 +1735,25 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
self.assertEquals(person1_s.getLastName(), self.last_name3)
self.checkSynchronizationStateIsSynchronized()
def test_36_SynchronizationSubscriptionMaxLines(self, quiet=0, run=run_all_test):
def test_36_SynchronizationSubscriptionMaxLines(self):
# We will try to populate the folder person_server
# with the data form person_client
# with the data which over max line of messages
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Synchronization Subscription Max Lines')
LOG('Testing... ',0,'test_36_SynchronizationSubscriptionMaxLines')
self.login()
self.setupPublicationAndSubscription(quiet=1, run=1)
nb_person = self.populatePersonClient1(quiet=1, run=1)
self.setupPublicationAndSubscription()
nb_person = self.populatePersonClient1()
portal_sync = self.getSynchronizationTool()
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(), SyncCode.SLOW_SYNC)
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
# Synchronize the first client
# data_Sub1 -> Pub (the data are in sub1 to pub is empty)
nb_message1 = self.synchronize(self.sub_id1)
#Verification number object synchronized
self.assertEqual(nb_message1, self.nb_message_first_sync_max_lines)
self.assertEquals(nb_message1, self.nb_message_first_sync_max_lines)
# Synchronize the second client
# data_Pub -> data_Sub2 the data are in pub to sub2 is empty so add +2 messages)
nb_message2 = self.synchronize(self.sub_id2)
self.assertEqual(nb_message2, self.nb_message_first_sync_max_lines + 2)
self.assertEquals(nb_message2, self.nb_message_first_sync_max_lines + 2)
person_server = self.getPersonServer()
person_client1 = self.getPersonClient1()
person_client2 = self.getPersonClient2()
......@@ -1809,8 +1761,8 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
person_s = person_server._getOb(str(id))
person_c = person_client1._getOb(str(id))
self.assertXMLViewIsEqual(self.sub_id1, person_s, person_c)
self.assertEqual(nb_person, len(person_server.objectValues()))
self.assertEqual(nb_person, len(person_client2.objectValues()))
self.assertEquals(nb_person, len(person_server.objectValues()))
self.assertEquals(nb_person, len(person_client2.objectValues()))
def test_suite():
suite = unittest.TestSuite()
......
# -*- coding: utf-8 -*-
##############################################################################
# vim: set fileencoding=utf-8
#
......@@ -28,25 +29,15 @@
##############################################################################
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
# Needed in order to have a log file inside the current folder
os.environ['EVENT_LOG_FILE'] = os.path.join(os.getcwd(), 'zLOG.log')
os.environ['EVENT_LOG_SEVERITY'] = '-300'
from Testing import ZopeTestCase
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
import os, sys
from AccessControl.SecurityManagement import newSecurityManager
from Products.ERP5SyncML.Conduit.VCardConduit import VCardConduit
from Products.ERP5SyncML.SyncCode import SyncCode
from testERP5SyncML import TestERP5SyncMLMixin
import transaction
from zLOG import LOG
class TestERP5SyncMLVCard(TestERP5SyncMLMixin, ERP5TypeTestCase):
run_all_test = True
class TestERP5SyncMLVCard(TestERP5SyncMLMixin):
def getBusinessTemplateList(self):
"""
......@@ -57,138 +48,103 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin, ERP5TypeTestCase):
/person_client1 : empty
/person_client2 : empty
"""
return ('erp5_base','erp5_syncml')
def verifyFirstNameAndLastNameAreSynchronized(self, first_name,
last_name, person_server, person_client):
"""
verify if the first and last name are synchronized
"""
self.assertEqual(person_server.getFirstName(), first_name)
self.assertEqual(person_server.getLastName(), last_name)
self.assertEqual(person_client.getFirstName(), first_name)
self.assertEqual(person_client.getLastName(), last_name)
return ('erp5_base', 'erp5_syncml',)
def getTitle(self):
return 'testERP5SyncMLVCard'
def test_01_AddVCardPublication(self, quiet=0, run=run_all_test):
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Add a VCard Publication ')
LOG('Testing... ',0,'test_01_AddVCardPublication')
portal_id = self.getPortalName()
def addVCardPublication(self):
portal_sync = self.getSynchronizationTool()
portal_sync.manage_addPublication(title=self.pub_id,
publication_url=self.publication_url,
destination_path='/%s/person_server' % portal_id,
source_uri='Person',
query='objectValues',
xml_mapping='Person_exportAsVCard',
conduit='SharedVCardConduit',
gpg_key='',
synchronization_id_generator='generateNewId',
media_type='text/vcard',
activity_enabled=False,
authentication_format='b64',
authentication_type='syncml:auth-basic')
pub = portal_sync.getPublication(self.pub_id)
self.failUnless(pub is not None)
def test_02_AddVCardSubscription1(self, quiet=0, run=run_all_test):
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Add First VCard Subscription ')
LOG('Testing... ',0,'test_02_AddVCardSubscription1')
portal_id = self.getPortalId()
if getattr(portal_sync, self.pub_id, None) is None:
pub = portal_sync.newContent(portal_type='SyncML Publication',
id=self.pub_id,
url_string=self.publication_url,
source='person_server',
source_reference='Person',
list_method_id='objectValues',
xml_binding_generator_method_id='Person_exportAsVCard',
conduit_module_id='SharedVCardConduit',
synchronisation_id_generator_method_id='generateNewId')
pub.validate()
transaction.commit()
self.tic()
def addVCardSubscription1(self):
portal_sync = self.getSynchronizationTool()
portal_sync.manage_addSubscription(title=self.sub_id1,
publication_url=self.publication_url,
subscription_url=self.subscription_url1,
destination_path='/%s/person_client1' % portal_id,
source_uri='Person',
target_uri='Person',
query='objectValues',
xml_mapping='Person_exportAsVCard',
conduit='SharedVCardConduit',
gpg_key='',
synchronization_id_generator='generateNewId',
media_type='text/vcard',
activity_enabled=False,
login='fab',
password='myPassword')
sub = portal_sync.getSubscription(self.sub_id1)
self.failUnless(sub is not None)
def test_03_AddVCardSubscription2(self, quiet=0, run=run_all_test):
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Add Second VCard Subscription ')
LOG('Testing... ',0,'test_03_AddVCardSubscription2')
portal_id = self.getPortalId()
if getattr(portal_sync, self.sub_id1, None) is None:
sub = portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id1,
url_string=self.publication_url,
subscription_url_string=self.subscription_url1,
source='person_client1',
source_reference='Person',
destination_reference='Person',
list_method_id='objectValues',
xml_binding_generator_method_id='Person_exportAsVCard',
conduit_module_id='SharedVCardConduit',
synchronisation_id_generator_method_id='generateNewId',
user_id='fab',
password='myPassword')
sub.validate()
transaction.commit()
self.tic()
def addVCardSubscription2(self):
portal_sync = self.getSynchronizationTool()
portal_sync.manage_addSubscription(title=self.sub_id2,
publication_url=self.publication_url,
subscription_url=self.subscription_url2,
destination_path='/%s/person_client2' % portal_id,
source_uri='Person',
target_uri='Person',
query='objectValues',
xml_mapping='Person_exportAsVCard',
conduit='SharedVCardConduit',
gpg_key='',
synchronization_id_generator='generateNewId',
media_type='text/vcard',
activity_enabled=False,
login='fab',
password='myPassword')
sub = portal_sync.getSubscription(self.sub_id2)
self.failUnless(sub is not None)
def test_04_FirstVCardSynchronization(self, quiet=0, run=run_all_test):
if getattr(portal_sync, self.sub_id2, None) is None:
sub = portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id2,
url_string=self.publication_url,
subscription_url_string=self.subscription_url2,
source='person_client2',
source_reference='Person',
destination_reference='Person',
list_method_id='objectValues',
xml_binding_generator_method_id='Person_exportAsVCard',
conduit_module_id='SharedVCardConduit',
synchronisation_id_generator_method_id='generateNewId',
user_id='fab',
password='myPassword')
sub.validate()
transaction.commit()
self.tic()
def test_04_FirstVCardSynchronization(self):
# We will try to populate the folder person_client1
# with the data form person_server
if not run: return
if not quiet:
ZopeTestCase._print('\nTest First VCard Synchronization ')
LOG('Testing... ',0,'test_04_FirstVCardSynchronization')
self.login()
self.test_01_AddVCardPublication(quiet=True, run=True)
self.test_02_AddVCardSubscription1(quiet=True, run=True)
self.test_03_AddVCardSubscription2(quiet=True, run=True)
nb_person = self.populatePersonServer(quiet=1,run=1)
self.deletePublicationAndSubscriptionList()
if 'person_server' in self.portal.objectIds():
self.portal._delObject('person_server')
if 'person_client1' in self.portal.objectIds():
self.portal._delObject('person_client1')
if 'person_client2' in self.portal.objectIds():
self.portal._delObject('person_client2')
self.addVCardPublication()
self.addVCardSubscription1()
self.addVCardSubscription2()
nb_person = self.populatePersonServer()
portal_sync = self.getSynchronizationTool()
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(),SyncCode.SLOW_SYNC)
for sub in portal_sync.searchFolder(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
# Synchronize the first client
nb_message1 = self.synchronize(self.sub_id1)
for sub in portal_sync.getSubscriptionList():
if sub.getTitle() == self.sub_id1:
self.assertEquals(sub.getSynchronizationType(),SyncCode.TWO_WAY)
else:
self.assertEquals(sub.getSynchronizationType(),SyncCode.SLOW_SYNC)
self.failUnless(nb_message1==self.nb_message_first_synchronization)
for sub in portal_sync.searchFolder(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
self.assertEquals(nb_message1, self.nb_message_first_synchronization)
# Synchronize the second client
nb_message2 = self.synchronize(self.sub_id2)
for sub in portal_sync.getSubscriptionList():
self.assertEquals(sub.getSynchronizationType(),SyncCode.TWO_WAY)
self.failUnless(nb_message2==self.nb_message_first_synchronization)
for sub in portal_sync.searchFolder(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
self.assertEquals(nb_message2, self.nb_message_first_synchronization)
self.checkFirstSynchronization(id='1', nb_person=nb_person)
def test_05_basicVCardSynchronization(self, quiet=0, run=run_all_test):
def test_05_basicVCardSynchronization(self):
"""
synchronize two ERP5Sites using VCards
"""
if not run: return
if not quiet:
ZopeTestCase._print('\nTest Basic VCard Synchronization ')
LOG('Testing... ',0,'test_05_basicVCardSynchronization')
self.test_04_FirstVCardSynchronization(quiet=True, run=True)
portal_sync = self.getSynchronizationTool()
self.test_04_FirstVCardSynchronization()
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
person_client1 = self.getPersonClient1()
......@@ -203,29 +159,23 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin, ERP5TypeTestCase):
self.last_name3, person1_s, person1_c)
self.synchronize(self.sub_id1)
#after synchronization, a new person is create on the server
person1_s = person_server._getOb('2') #The new person is added on the
#serverwith a generated id (the following is 2)
person1_s = person_server._getOb('1')
#after the synchro, the client and server should be synchronized
self.checkSynchronizationStateIsSynchronized()
self.verifyFirstNameAndLastNameAreSynchronized(self.first_name3,
self.last_name3, person1_s, person1_c)
self.assertEqual(person1_s.getFirstName(), self.first_name3)
self.assertEqual(person1_s.getLastName(), self.last_name3)
self.assertEqual(person1_c.getFirstName(), self.first_name3)
self.assertEqual(person1_c.getLastName(), self.last_name3)
def test_05_verifyNoDuplicateDataWhenAdding(self, quiet=0, run=run_all_test):
def test_05_verifyNoDuplicateDataWhenAdding(self):
"""
this test permit to verify that if the server already have the person,
he don't add it a second time
"""
if not run: return
if not quiet:
ZopeTestCase._print('\nTest No Duplicate Data When Adding ')
LOG('Testing... ',0,'test_05_verifyNoDuplicateDataWhenAdding')
self.test_04_FirstVCardSynchronization(quiet=True, run=True)
self.test_04_FirstVCardSynchronization()
portal_sync = self.getSynchronizationTool()
sub1 = portal_sync.getSubscription(self.sub_id1)
sub2 = portal_sync.getSubscription(self.sub_id2)
pub = portal_sync.getPublication(self.pub_id)
pub = portal_sync[self.pub_id]
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
person_client1 = self.getPersonClient1()
......@@ -237,8 +187,10 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin, ERP5TypeTestCase):
person1_c.edit(**kw)
person1_s.edit(**kw) #the same person is added on client AND server
#before synchornization, First and Last name souldn't be the same
self.verifyFirstNameAndLastNameAreSynchronized(self.first_name3,
self.last_name3, person1_s, person1_c)
self.assertEquals(person1_s.getFirstName(), self.first_name3)
self.assertEquals(person1_s.getLastName(), self.last_name3)
self.assertEquals(person1_c.getFirstName(), self.first_name3)
self.assertEquals(person1_c.getLastName(), self.last_name3)
nb_person_serv_before_sync = len(pub.getObjectList())
self.synchronize(self.sub_id1)
#after synchronization, no new person is created on server because it
......@@ -248,22 +200,20 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin, ERP5TypeTestCase):
#after the synchro, the client and server should be synchronized
self.checkSynchronizationStateIsSynchronized()
self.verifyFirstNameAndLastNameAreSynchronized(self.first_name3,
self.last_name3, person1_s, person1_c)
self.assertEquals(person1_s.getFirstName(), self.first_name3)
self.assertEquals(person1_s.getLastName(), self.last_name3)
self.assertEquals(person1_c.getFirstName(), self.first_name3)
self.assertEquals(person1_c.getLastName(), self.last_name3)
nb_person_serv_after_sync = len(pub.getObjectList())
#the number of person on server before and after the synchronization should
#be the same
nb_person_serv_after_sync = len(pub.getObjectList())
self.failUnless(nb_person_serv_after_sync==nb_person_serv_before_sync)
self.assertEquals(nb_person_serv_after_sync, nb_person_serv_before_sync)
if __name__ == '__main__':
framework()
else:
import unittest
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestERP5SyncMLVCard))
return suite
import unittest
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestERP5SyncMLVCard))
return suite
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment