XMLSyncUtils.py 55.4 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
##############################################################################
#
# 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.
#
##############################################################################

import smtplib
from Products.ERP5SyncML.SyncCode import SyncCode
from Products.ERP5SyncML.Subscription import Signature
32
from DateTime import DateTime
33
from StringIO import StringIO
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34
from xml.dom.ext import PrettyPrint
35
import random
Nicolas Delaby's avatar
Nicolas Delaby committed
36
from zLOG import LOG
37 38 39 40 41 42
try:
  from Products.CMFActivity.ActiveObject import ActiveObject
except ImportError:
  LOG('XMLSyncUtils',0,"Can't import ActiveObject")
  class ActiveObject:
    pass
Jean-Paul Smets's avatar
Jean-Paul Smets committed
43
import commands
Nicolas Delaby's avatar
Nicolas Delaby committed
44

45 46 47 48 49 50 51
try:
  from Ft.Xml import Parse
except ImportError:
  LOG('XMLSyncUtils',0,"Can't import Parse")
  class Parse:
    def __init__(self, *args, **kw):
      raise ImportError, "Sorry, it was not possible to import Ft library"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
52

53 54 55 56 57
try:
      from base64 import b16encode, b16decode
except ImportError:
      from base64 import encodestring as b16encode, decodestring as b16decode

58
class XMLSyncUtilsMixin(SyncCode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
59

Sebastien Robin's avatar
Sebastien Robin committed
60
  def SyncMLHeader(self, session_id, msg_id, target, source, target_name=None, 
61 62
      source_name=None, dataCred=None, authentication_format='b64', 
      authentication_type='syncml:auth-basic'):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
63 64 65 66
    """
      Since the Header is always almost the same, this is the
      way to set one quickly.
    """
Sebastien Robin's avatar
Sebastien Robin committed
67 68 69 70 71 72 73 74 75
    xml_list = []
    xml = xml_list.append
    xml(' <SyncHdr>\n')
    xml('  <VerDTD>1.1</VerDTD>\n')
    xml('  <VerProto>SyncML/1.1</VerProto>\n')
    xml('  <SessionID>%s</SessionID>\n' % session_id)
    xml('  <MsgID>%s</MsgID>\n' % msg_id)
    xml('  <Target>\n')
    xml('   <LocURI>%s</LocURI>\n' % target)
76
    if target_name not in (None, ''):
Sebastien Robin's avatar
Sebastien Robin committed
77 78 79 80
      xml('   <LocName>%s</LocName>\n' %target_name)
    xml('  </Target>\n')
    xml('  <Source>\n')
    xml('   <LocURI>%s</LocURI>\n' % source) 
81
    if source_name not in (None, ''):
Sebastien Robin's avatar
Sebastien Robin committed
82 83
      xml('   <LocName>%s</LocName>\n' % source_name)
    xml('  </Source>\n')
84 85 86
    if dataCred not in (None, ''):
      xml('  <Cred>\n')
      xml('   <Meta>\n')
87 88
      xml("    <Format xmlns='syncml:metinf'>%s</Format>\n" % authentication_format)
      xml("    <Type xmlns='syncml:metinf'>%s</Type>\n" % authentication_type)
89 90 91
      xml('   </Meta>\n')
      xml('   <Data>%s</Data>\n' % dataCred)
      xml('  </Cred>\n')
Sebastien Robin's avatar
Sebastien Robin committed
92 93 94
    xml(' </SyncHdr>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
95

Sebastien Robin's avatar
Sebastien Robin committed
96 97
  def SyncMLAlert(self, cmd_id, sync_code, target, source, last_anchor, 
      next_anchor):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
98 99 100 101
    """
      Since the Alert section is always almost the same, this is the
      way to set one quickly.
    """
Sebastien Robin's avatar
Sebastien Robin committed
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
    xml_list = []
    xml = xml_list.append
    xml('  <Alert>\n')
    xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    xml('   <Data>%s</Data>\n' % sync_code)
    xml('   <Item>\n')
    xml('    <Target>\n')
    xml('     <LocURI>%s</LocURI>\n' % target)
    xml('    </Target>\n')
    xml('    <Source>\n')
    xml('     <LocURI>%s</LocURI>\n' % source)
    xml('    </Source>\n')
    xml('    <Meta>\n')
    xml('     <Anchor xmlns=\'syncml:metinf\'>\n')
    xml('      <Last>%s</Last>\n' % last_anchor)
    xml('      <Next>%s</Next>\n' % next_anchor)
    xml('     </Anchor>\n')
    xml('    </Meta>\n')
    xml('   </Item>\n')
    xml('  </Alert>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
124

Sebastien Robin's avatar
Sebastien Robin committed
125
  def SyncMLStatus(self, cmd_id, target_ref, source_ref, sync_code, 
126
      next_anchor=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
127 128 129 130
    """
      Since the Status section is always almost the same, this is the
      way to set one quickly.
    """
Sebastien Robin's avatar
Sebastien Robin committed
131 132 133 134 135 136 137
    xml_list = []
    xml = xml_list.append
    xml('  <Status>\n')
    xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    xml('   <TargetRef>%s</TargetRef>\n' % target_ref)
    xml('   <SourceRef>%s</SourceRef>\n' % source_ref)
    xml('   <Data>%s</Data>\n' % sync_code)
138 139 140 141 142 143 144 145
    if next_anchor is not None:
      xml('   <Item>\n')
      xml('    <Data>\n')
      xml('     <Anchor xmlns=\'syncml:metinf\'>\n')
      xml('      <Next>%s</Next>\n' % next_anchor)
      xml('     </Anchor>\n')
      xml('    </Data>\n')
      xml('   </Item>\n')
Sebastien Robin's avatar
Sebastien Robin committed
146 147 148
    xml('  </Status>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
149

150 151 152
  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):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
153
    """
Sebastien Robin's avatar
Sebastien Robin committed
154
    This is used in order to confirm that an object was correctly
Jean-Paul Smets's avatar
Jean-Paul Smets committed
155 156
    synchronized
    """
157 158 159 160 161 162
    if remote_xml is not None :
      msg_ref=remote_xml.xpath("string(//MsgID)").encode('utf-8')
      cmd_ref=remote_xml.xpath("string(.//CmdID)").encode('utf-8')
      target_ref=remote_xml.xpath("string(.//Target/LocURI)").encode('utf-8')
      source_ref=remote_xml.xpath("string(.//Source/LocURI)").encode('utf-8')

Sebastien Robin's avatar
Sebastien Robin committed
163 164 165
    xml_list = []
    xml = xml_list.append
    xml('  <Status>\n')
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    #here there is a lot of test to keep compatibility with older call
    if cmd_id not in (None,'') :
      xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    if msg_ref not in (None,''):
      xml('   <MsgRef>%s</MsgRef>\n' % msg_ref)
    if cmd_ref not in (None,''):
      xml('   <CmdRef>%s</CmdRef>\n' %cmd_ref)
    if cmd not in (None,''):
      xml('   <Cmd>%s</Cmd>\n' % cmd)
    if target_ref not in (None,''):
      xml('   <TargetRef>%s</TargetRef>\n' % target_ref)
    if source_ref not in (None,''):
      xml('   <SourceRef>%s</SourceRef>\n' % source_ref)
    if sync_code not in (None,''):
      xml('   <Data>%s</Data>\n' % sync_code)
Sebastien Robin's avatar
Sebastien Robin committed
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
    xml('  </Status>\n')
    xml_a = ''.join(xml_list)
    return xml_a
    
  def SyncMLChal(self, cmd_id, cmd, target_ref, source_ref, auth_format, 
      auth_type, data_code):
    """
    This is used in order to ask crendentials
    """
    xml_list = []
    xml = xml_list.append
    xml('  <Status>\n')
    xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    xml('   <Cmd>%s</Cmd>\n' % cmd)
    xml('   <TargetRef>%s</TargetRef>\n' % target_ref)
    xml('   <SourceRef>%s</SourceRef>\n' % source_ref)
    xml('   <Chal>\n')
    xml('    <Meta>\n')
199 200
    xml("     <Format xmlns='syncml:metinf'>%s</Format>\n" % auth_format)
    xml("     <Type xmlns='syncml:metinf'>%s</Type>\n" % auth_type)
Sebastien Robin's avatar
Sebastien Robin committed
201 202 203 204 205 206
    xml('    </Meta>\n')
    xml('   </Chal>\n')
    xml('   <Data>%s</Data>\n' % str(data_code))
    xml('  </Status>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
207

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
  def SyncMLPut(self, cmd_id, subscription):
    """
    this is used to inform the server of the CTType version supported
    """
    from Products.ERP5SyncML import Conduit
    # Import the conduit and get it
    conduit_name = subscription.getConduit()
    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)()
    #if the conduit support the SyncMLPut :
    if hasattr(conduit, 'getCapabilitiesCTType') and \
        hasattr(conduit, 'getCapabilitiesVerCTList') and \
        hasattr(conduit, 'getPreferedCapabilitieVerCT'):
      xml_list = []
      xml = xml_list.append
      if conduit.getCapabilitiesVerCTList() not in ([], None):
        xml('  <Put>\n')
        xml('   <CmdID>%s</CmdID>\n' % cmd_id)
        xml('   <Meta>\n')
        xml('    <Type>application/vnd.syncml-devinf+xml</Type>\n');
        xml('   </Meta>\n')
        xml('   <Item>\n')
        xml('    <Source>\n')
        xml('     <LocURI>./devinf11</LocURI>\n')
        xml('    </Source>\n')
        xml('    <Data>\n')
        xml('     <DevInf>\n')
        xml('      <VerDTD>1.1</VerDTD>\n')
        xml('      <Man>Fabien MORIN</Man>\n')
        xml('      <Mod>ERP5SyncML</Mod>\n')
        xml('      <OEM>Open Source</OEM>\n')
        xml('      <SwV>0.1</SwV>\n')
        xml('      <DevID>%s</DevID>\n' % subscription.getSubscriptionUrl())
        xml('      <DevTyp>workstation</DevTyp>\n')
        xml('      <UTC/>\n')
        xml('      <DataStore>\n')
        xml('       <SourceRef>%s</SourceRef>\n' % subscription.getSourceURI())
        xml('       <Rx-Pref>\n')
        xml('        <CTType>%s</CTType>\n' % conduit.getCapabilitiesCTType())
        xml('        <VerCT>%s</VerCT>\n' % conduit.getPreferedCapabilitieVerCT())
        xml('       </Rx-Pref>\n')
        for rx_version in conduit.getCapabilitiesVerCTList():
          xml('       <Rx>\n')
          xml('        <CTType>%s</CTType>\n' % conduit.getCapabilitiesCTType())
          xml('        <VerCT>%s</VerCT>\n' % rx_version)
          xml('       </Rx>\n')

        xml('       <Tx-Pref>\n')
        xml('        <CTType>%s</CTType>\n' % conduit.getCapabilitiesCTType())
        xml('        <VerCT>%s</VerCT>\n' % conduit.getPreferedCapabilitieVerCT())
        xml('       </Tx-Pref>\n')
        for tx_version in conduit.getCapabilitiesVerCTList():
          xml('       <Tx>\n')
          xml('        <CTType>%s</CTType>\n' % conduit.getCapabilitiesCTType())
          xml('        <VerCT>%s</VerCT>\n' % rx_version)
          xml('       </Tx>\n')

        xml('       <SyncCap>\n')
        xml('        <SyncType>2</SyncType>\n')
        xml('        <SyncType>1</SyncType>\n')
        xml('        <SyncType>4</SyncType>\n')
        xml('        <SyncType>6</SyncType>\n')
        xml('       </SyncCap>\n')

        xml('      </DataStore>\n')
        xml('     </DevInf>\n')
        xml('    </Data>\n')
        xml('   </Item>\n')
        xml('  </Put>\n')
      xml_a = ''.join(xml_list)
      return xml_a
    return ''


Jean-Paul Smets's avatar
Jean-Paul Smets committed
289 290 291 292 293 294 295 296 297
  def sendMail(self, fromaddr, toaddr, id_sync, msg):
    """
      Send a message via email
      - sync_object : this is a publication or a subscription
      - message : what we want to send
    """
    header = "Subject: %s\n" % id_sync
    header += "To: %s\n\n" % toaddr
    msg = header + msg
Nicolas Delaby's avatar
Nicolas Delaby committed
298
    #LOG('SubSendMail',0,'from: %s, to: %s' % (fromaddr,toaddr))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
299 300 301
    server = smtplib.SMTP('localhost')
    server.sendmail(fromaddr, toaddr, msg)
    # if we want to send the email to someone else (debugging)
302
    #server.sendmail(fromaddr, "seb@localhost", msg)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
303 304
    server.quit()

305
  def addXMLObject(self, cmd_id=0, object=None, xml_string=None,
306
                  more_data=0,gid=None, media_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
307 308 309
    """
      Add an object with the SyncML protocol
    """
Sebastien Robin's avatar
Sebastien Robin committed
310 311 312 313
    xml_list = []
    xml = xml_list.append
    xml('   <Add>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
314
    xml('    <Meta>\n')
315
    xml('     <Type>%s</Type>\n' % media_type)
316
    xml('    </Meta>\n')
Sebastien Robin's avatar
Sebastien Robin committed
317 318 319 320
    xml('    <Item>\n')
    xml('     <Source>\n')
    xml('      <LocURI>%s</LocURI>\n' % gid)
    xml('     </Source>\n')
321 322 323 324 325 326 327 328
    if media_type == self.MEDIA_TYPE['TEXT_XML']:
      xml('     <Data>')
      xml(xml_string)
      xml('</Data>\n')
    else:
      xml('     <Data><![CDATA[')
      xml(xml_string)
      xml('\n]]></Data>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
329
    if more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
330 331 332 333 334
      xml('     <MoreData/>\n')
    xml('    </Item>\n')
    xml('   </Add>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
335

336
  def deleteXMLObject(self, cmd_id=0, object_gid=None, xml_object=''):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
337
    """
Sebastien Robin's avatar
Sebastien Robin committed
338 339 340 341 342 343 344 345 346 347
      Delete an object with the SyncML protocol
    """
    xml_list = []
    xml = xml_list.append
    xml('   <Delete>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
    xml('    <Item>\n')
    xml('     <Source>\n')
    xml('      <LocURI>%s</LocURI>\n' % object_gid)
    xml('     </Source>\n')
348 349
    #xml('     <Data>\n')  #this 2 lines seems to be useless
    #xml('     </Data>\n')
Sebastien Robin's avatar
Sebastien Robin committed
350 351 352 353
    xml('    </Item>\n')
    xml('   </Delete>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
354

355
  def replaceXMLObject(self, cmd_id=0, object=None, xml_string=None,
356
                       more_data=0, gid=None, media_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
357
    """
Sebastien Robin's avatar
Sebastien Robin committed
358 359 360 361 362 363
      Replace an object with the SyncML protocol
    """
    xml_list = []
    xml = xml_list.append
    xml('   <Replace>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
364
    xml('    <Meta>\n')
365
    xml('     <Type>%s</Type>\n' % media_type)
366
    xml('    </Meta>\n')
Sebastien Robin's avatar
Sebastien Robin committed
367 368 369 370
    xml('    <Item>\n')
    xml('     <Source>\n')
    xml('      <LocURI>%s</LocURI>\n' % str(gid))
    xml('     </Source>\n')
371
    xml('     <Data>')
Sebastien Robin's avatar
Sebastien Robin committed
372 373
    xml(xml_string)
    xml('     </Data>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
374
    if more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
375 376 377 378 379
      xml('     <MoreData/>\n')
    xml('    </Item>\n')
    xml('   </Replace>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
380 381 382 383 384 385 386 387

  def getXupdateObject(self, object=None, xml_mapping=None, old_xml=None):
    """
    Generate the xupdate with the new object and the old xml
    We have to use xmldiff as a command line tool, because all
    over the xmldiff code, there's some print to the standard
    output, so this is unusable
    """
388 389 390 391
    filename = str(random.randrange(1,2147483600))
    old_filename = filename + '.old'
    new_filename = filename + '.new'
    file1 = open('/tmp/%s' % new_filename,'w')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
392 393
    file1.write(self.getXMLObject(object=object,xml_mapping=xml_mapping))
    file1.close()
394
    file2 = open('/tmp/%s'% old_filename,'w')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
395 396
    file2.write(old_xml)
    file2.close()
Sebastien Robin's avatar
Sebastien Robin committed
397 398
    xupdate = commands.getoutput('erp5diff /tmp/%s /tmp/%s' % 
        (old_filename,new_filename))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
399
    xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
400 401
    commands.getstatusoutput('rm -f /tmp/%s' % old_filename)
    commands.getstatusoutput('rm -f /tmp/%s' % new_filename)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
402 403 404 405 406 407
    return xupdate

  def getXMLObject(self, object=None, xml_mapping=None):
    """
    This just allow to get the xml of the object
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
408 409
    if xml_mapping in ['None', None]:
      return ''
Jean-Paul Smets's avatar
Jean-Paul Smets committed
410 411
    xml_method = None
    xml = ""
412 413 414
    if xml_mapping is not None:
      if hasattr(object,xml_mapping):
        xml_method = getattr(object,xml_mapping)
415 416
      #elif hasattr(object,'manage_FTPget'):
      #  xml_method = getattr(object,'manage_FTPget')
417 418
      if xml_method is not None:
        xml = xml_method()
419 420
      else:
        raise ValueError, "Sorry the script or method was not found"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
421 422
    return xml

423 424 425 426 427
  def getSessionId(self, xml):
    """
    We will retrieve the session id of the message
    """
    session_id = 0
428 429
    session_id = xml.xpath('string(/SyncML/SyncHdr/SessionID)')
    session_id = int(session_id)
430
    return session_id
Sebastien Robin's avatar
Sebastien Robin committed
431 432 433 434 435 436
    
  def getMessageId(self, xml):
    """
    We will retrieve the message id of the message
    """
    message_id = 0
437 438
    message_id = xml.xpath('string(/SyncML/SyncHdr/MsgID)')
    message_id = int(message_id)
Sebastien Robin's avatar
Sebastien Robin committed
439 440 441 442 443 444 445
    return message_id

  def getTarget(self, xml):
    """
    return the target in the SyncHdr section
    """
    url = ''
446 447
    url = xml.xpath('string(/SyncML/SyncHdr/Target/LocURI)')
    url = url.encode('utf-8')
Sebastien Robin's avatar
Sebastien Robin committed
448 449
    return url

Jean-Paul Smets's avatar
Jean-Paul Smets committed
450 451 452 453 454
  def getAlertLastAnchor(self, xml_stream):
    """
      Return the value of the last anchor, in the
      alert section of the xml_stream
    """
Sebastien Robin's avatar
Sebastien Robin committed
455
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
456 457 458

    # Get informations from the body
    client_body = first_node.childNodes[3]
459 460
    last_anchor = client_body.xpath('string(/Alert/Item/Meta/Anchor/Last)')
    last_anchor = last_anchor.encode('utf-8')
Sebastien Robin's avatar
Sebastien Robin committed
461
    return last_anchor
Jean-Paul Smets's avatar
Jean-Paul Smets committed
462 463 464 465 466 467

  def getAlertNextAnchor(self, xml_stream):
    """
      Return the value of the next anchor, in the
      alert section of the xml_stream
    """
Sebastien Robin's avatar
Sebastien Robin committed
468
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
469 470 471 472 473
    if first_node.nodeName != "SyncML":
      print "This is not a SyncML message"

    # Get informations from the body
    client_body = first_node.childNodes[3]
474 475 476
    next_anchor = client_body.xpath('string(/Alert/Item/Meta/Anchor/Next)')
    next_anchor = next_anchor.encode('utf-8')
    return next_anchor
Jean-Paul Smets's avatar
Jean-Paul Smets committed
477 478 479 480 481

  def getStatusTarget(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
482 483 484 485
    status = xml.xpath('string(TargetRef)')
    if isinstance(status, unicode):
      status = status.encode('utf-8')
    return status
Jean-Paul Smets's avatar
Jean-Paul Smets committed
486 487 488 489 490

  def getStatusCode(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
491 492 493
    status_code = xml.xpath('string(Data)')
    if status_code not in ('', None, []):
      return int(status_code)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
494 495
    return None

496 497 498 499 500
  def getStatusCommand(self, xml):
    """
      Return the value of the command inside the xml_stream
    """
    if xml.nodeName=='Status':
501 502 503 504
      cmd = xml.xpath('string(//Status/Cmd)')
      if isinstance(cmd, unicode):
        cmd = cmd.encode('utf-8')
      return cmd
505 506
    else:
      return None
507

508 509 510
  def getCred(self, xml):
    """
      return the credential information : type, format and data
Nicolas Delaby's avatar
Nicolas Delaby committed
511
    """
512 513 514 515 516
    format=''
    type=''
    data=''

    first_node = xml.childNodes[0]
517 518
    format = first_node.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Format'])")
    type = first_node.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Type'])")
519
    data = first_node.xpath('string(/SyncML/SyncHdr/Cred/Data)')
520

521 522 523
    format = format.encode('utf-8')
    type = type.encode('utf-8')
    data = data.encode('utf-8')
524 525
    return (format, type, data)

526
  def checkCred(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
527
    """
528
      Check if there's a Cred section in the xml_stream
Jean-Paul Smets's avatar
Jean-Paul Smets committed
529
    """
530 531 532
    if xml_stream.xpath('string(SyncML/SyncHdr/Cred)') not in ('', None, []):
      return True
    return False
Jean-Paul Smets's avatar
Jean-Paul Smets committed
533

534
  def getChal(self, xml):
Sebastien Robin's avatar
Sebastien Robin committed
535
    """
536 537
      return the chalenge information : format and type
    """    
538 539
    format=None
    type=None
540 541

    first_node = xml.childNodes[0]
542 543
    format = first_node.xpath("string(//*[local-name() = 'Format'])")
    type = first_node.xpath("string(//*[local-name() = 'Type'])")
544 545 546 547 548 549

    format = format.encode('utf-8')
    type = type.encode('utf-8')
    return (format, type)

  def checkChal(self, xml_stream):
Sebastien Robin's avatar
Sebastien Robin committed
550
    """
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
      Check if there's a Chal section in the xml_stream
    """
    if xml_stream.xpath('string(SyncML/SyncBody/Status/Chal)') \
        not in ('', None, []):
      return True
    return False

  def getAlertCode(self, xml_stream):
    """
      Return the value of the alert code inside the full syncml message
    """
    alert_code = xml_stream.xpath('string(SyncML/SyncBody/Alert/Data)')
    if alert_code not in (None, ''):
      return int(alert_code)
    else:
      return None
Sebastien Robin's avatar
Sebastien Robin committed
567

Jean-Paul Smets's avatar
Jean-Paul Smets committed
568 569
  def checkAlert(self, xml_stream):
    """
Sebastien Robin's avatar
Sebastien Robin committed
570
      Check if there's an Alert section in the xml_stream
Jean-Paul Smets's avatar
Jean-Paul Smets committed
571
    """
572 573 574
    alert = False
    if xml_stream.xpath('string(SyncML/SyncBody/Alert)') not in ('', None, []):
      alert = True
Jean-Paul Smets's avatar
Jean-Paul Smets committed
575 576 577 578 579 580
    return alert

  def checkSync(self, xml_stream):
    """
      Check if there's an Sync section in the xml_xtream
    """
581 582 583 584
    sync = False
    if xml_stream.xpath('string(SyncML/SyncBody/Sync)') not in ('', None, []):
      sync = True
    return sync
Jean-Paul Smets's avatar
Jean-Paul Smets committed
585

586
  def checkStatus(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
587 588 589
    """
      Check if there's a Status section in the xml_xtream
    """
590 591 592
    status = False
    if xml_stream.xpath('string(SyncML/SyncBody/Status)') not in ('', None, []):
      status = True
Jean-Paul Smets's avatar
Jean-Paul Smets committed
593 594
    return status

595
  def getSyncActionList(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
596
    """
597
    return the list of the action (could be "add", "replace", "delete").
Jean-Paul Smets's avatar
Jean-Paul Smets committed
598
    """
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
    return xml_stream.xpath('//Add|//Delete|//Replace')

  def getSyncBodyStatusList(self, xml_stream):
    """
    return the list of dictionary corredponding to the data of each status bloc
    the data are : cmd, code and source
    """
    status_list = []
    xml = xml_stream.xpath('//Status')
    for status in xml:
      tmp_dict = {}
      tmp_dict['cmd']     = status.xpath('string(./Cmd)').encode('utf-8')
      tmp_dict['code']    = status.xpath('string(./Data)').encode('utf-8')
      tmp_dict['source']  = status.xpath('string(./SourceRef)').encode('utf-8')
      tmp_dict['target']  = status.xpath('string(./TargetRef)').encode('utf-8')
      status_list.append(tmp_dict)

    return status_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
617 618 619

  def getNextSyncBodyStatus(self, xml_stream, last_status):
    """
620 621 622
    It goes throw actions in the Sync section of the SyncML file,
    then it returns the next action (could be "add", "replace",
    "delete").
Jean-Paul Smets's avatar
Jean-Paul Smets committed
623
    """
Sebastien Robin's avatar
Sebastien Robin committed
624
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
625 626
    client_body = first_node.childNodes[3]
    if client_body.nodeName != "SyncBody":
Nicolas Delaby's avatar
Nicolas Delaby committed
627
      #LOG('getNextSyncBodyStatus',0,"This is not a SyncML Body")
Sebastien Robin's avatar
Sebastien Robin committed
628
      raise ValueError, "Sorry, This is not a SyncML Body"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
629 630 631
    next_status = None
    found = None
    for subnode in client_body.childNodes:
632 633
      if subnode.nodeType == subnode.ELEMENT_NODE and \
          subnode.nodeName == "Status":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
634 635 636 637 638 639 640 641 642 643
        # if we didn't use this method before
        if last_status == None:
          next_status = subnode
          return next_status
        elif subnode == last_status and found is None:
          found = 1
        elif found is not None:
          return subnode
    return next_status

644 645 646 647
  def getDataText(self, action):
    """
    return the section data in text form, it's usefull for the VCardConduit
    """
648 649 650 651
    data = action.xpath('string(Item/Data)')
    if isinstance(data, unicode):
      data = data.encode('utf-8')
    return data
652

Jean-Paul Smets's avatar
Jean-Paul Smets committed
653 654 655 656
  def getDataSubNode(self, action):
    """
      Return the node starting with <object....> of the action
    """
657 658 659 660
    if action.xpath('.//Item/Data') not in ([], None):
      data_node = action.xpath('.//Item/Data')[0]
      if data_node.hasChildNodes():
        return data_node.childNodes[0]
661
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
662 663 664 665 666 667

  def getPartialData(self, action):
    """
      Return the node starting with <object....> of the action
    """
    for subnode in action.childNodes:
668 669
      if subnode.nodeType == subnode.ELEMENT_NODE and \
          subnode.nodeName == 'Item':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
670
        for subnode2 in subnode.childNodes:
671 672
          if subnode2.nodeType == subnode2.ELEMENT_NODE and \
              subnode2.nodeName == 'Data':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
673 674 675 676 677 678 679
            for subnode3 in subnode2.childNodes:
              #if subnode3.nodeType == subnode3.ELEMENT_NODE and subnode3.nodeName == 'object':
              if subnode3.nodeType == subnode3.COMMENT_NODE:
                # No need to remove comment, it is already done by FromXml
                #if subnode3.data.find('<!--')>=0:
                #  data = subnode3.data
                #  data = data[data.find('<!--')+4:data.rfind('-->')]
680
                xml = subnode3.data
Sebastien Robin's avatar
Sebastien Robin committed
681
		if isinstance(xml, unicode):
682 683
                  xml = xml.encode('utf-8')
                return xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
684 685 686

    return None

687
  def getActionId(self, action, action_name):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
688 689 690
    """
      Return the rid of the object described by the action
    """
691 692 693 694 695 696
    id = action.xpath('string(.//Item/Source/LocURI)')
    if id in (None, ''):
      id = action.xpath('string(.//Item/Target/LocURI)')
    if isinstance(id, unicode):
      id = id.encode('utf-8')
    return id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
697 698 699 700 701

  def checkActionMoreData(self, action):
    """
      Return the rid of the object described by the action
    """
702 703 704
    if action.xpath('Item/MoreData') not in ([],None) :
      return True
    return False
Jean-Paul Smets's avatar
Jean-Paul Smets committed
705 706 707 708 709

  def getActionType(self, action):
    """
      Return the type of the object described by the action
    """
710 711 712 713
    action_type = action.xpath('string(Meta/Type)')
    if isinstance(action_type, unicode):
      action_type = action_type.encode('utf-8')
    return action_type
Jean-Paul Smets's avatar
Jean-Paul Smets committed
714

715 716 717 718
  def getElementNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
719
    return node.xpath('*')
720

721 722 723 724 725
  def getTextNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
    subnode_list = []
726
    for subnode in node.childNodes or []:
727 728 729
      if subnode.nodeType == subnode.TEXT_NODE:
        subnode_list += [subnode]
    return subnode_list
730
  
731 732 733 734
  def getAttributeNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
735
    return node.xpath('@*')
736

737
  def getSyncMLData(self, domain=None,remote_xml=None,cmd_id=0,
738
                          subscriber=None,destination_path=None,
739
                          xml_confirmation=None,conduit=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
740
    """
741 742
    This generate the syncml data message. This returns a string
    with all modification made locally (ie replace, add ,delete...)
743 744 745

    if object is not None, this usually means we want to set the
    actual xupdate on the signature.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
746
    """
747 748
    local_gid_list = []
    syncml_data = ''
749

750
    if subscriber.getRemainingObjectPathList() is None:
751
      object_list = domain.getObjectList()
752 753
      object_path_list = map(lambda x: x.getPhysicalPath(),object_list)
      subscriber.setRemainingObjectPathList(object_path_list)
754 755 756 757 758 759 760

      #object_gid = domain.getGidFromObject(object)
      local_gid_list = map(lambda x: domain.getGidFromObject(x),object_list)
      # Objects to remove
      for object_gid in subscriber.getGidList():
        if not (object_gid in local_gid_list):
          # This is an object to remove
761
          signature = subscriber.getSignatureFromGid(object_gid)
762 763 764
          if signature.getStatus()!=self.PARTIAL: # If partial, then we have a signature
                                                  # but no local object
            xml_object = signature.getXML()
765 766
            if xml_object is not None: # This prevent to delete an object that
                                      # we were not able to create
767 768 769
              rid = signature.getRid()
              if rid != None:
                object_gid=rid #to use the remote id if it exist
770 771
              syncml_data += self.deleteXMLObject(xml_object=signature.getXML()\
                  or '', object_gid=object_gid,cmd_id=cmd_id)
772
              cmd_id += 1
773

774
    local_gid_list = []
775
    #for object in domain.getObjectList():
776 777 778 779 780 781 782
    for object_path in subscriber.getRemainingObjectPathList():
      #object = subscriber.getDestination()._getOb(object_id)
      #object = subscriber.getDestination()._getOb(object_id)
      #try:
      object = self.unrestrictedTraverse(object_path)
      #except KeyError:
      #object = None
783
      status = self.SENT
784 785
      #gid_generator = getattr(object,domain.getGidGenerator(),None)
      object_gid = domain.getGidFromObject(object)
786 787
      if object_gid in ('', None):
        continue;
788 789 790
      local_gid_list += [object_gid]
      #if gid_generator is not None:
      #  object_gid = gid_generator()
791
      force = 0
792 793
      if syncml_data.count('\n') < self.MAX_LINES and not \
          object.id.startswith('.'): # If not we have to cut
794 795 796
        #LOG('getSyncMLData',0,'xml_mapping: %s' % str(domain.xml_mapping))
        #LOG('getSyncMLData',0,'code: %s' % str(self.getAlertCode(remote_xml)))
        #LOG('getSyncMLData',0,'gid_list: %s' % str(local_gid_list))
797
        #LOG('getSyncMLData',0,'subscriber.getGidList: %s' % subscriber.getGidList())
798 799
        #LOG('getSyncMLData',0,'hasSignature: %s' % str(subscriber.hasSignature(object_gid)))
        #LOG('getSyncMLData',0,'alert_code == slowsync: %s' % str(self.getAlertCode(remote_xml)==self.SLOW_SYNC))
800
        signature = subscriber.getSignatureFromGid(object_gid)
801

802
        # Here we first check if the object was modified or not by looking at dates
803
        if signature is not None:
804
          signature.checkSynchronizationNeeded(object)
805 806 807
        status = self.SENT
        more_data=0
        # For the case it was never synchronized, we have to send everything
808 809
        if signature is not None and signature.getXMLMapping()==None:
          pass
810 811 812
        elif signature == None or (signature.getXML() == None and \
            signature.getStatus() != self.PARTIAL) or \
            self.getAlertCode(remote_xml) == self.SLOW_SYNC:
813
          #LOG('PubSyncModif',0,'Current object.getPath: %s' % object.getPath())
814 815
          xml_object = self.getXMLObject(object=object, 
              xml_mapping=domain.xml_mapping)
816
          xml_string = xml_object
817 818 819 820
          if isinstance(xml_string, unicode):
            xml_string = xml_object.encode('utf-8')
          gid = subscriber.getGidFromObject(object)
          signature = Signature(id=gid,object=object)
821 822
          signature.setTempXML(xml_object)
          if xml_string.count('\n') > self.MAX_LINES:
823
            if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
824
              xml_string = xml_string.replace('--','@-@@-@')
825 826 827 828 829 830 831 832
            more_data=1
            i = 0
            short_string = ''
            rest_string = xml_string
            while i < self.MAX_LINES:
              short_string += rest_string[:rest_string.find('\n')+1]
              rest_string = xml_string[len(short_string):]
              i += 1
833
            #LOG('getSyncMLData',0,'setPartialXML with: %s' % str(rest_string))
834
            signature.setPartialXML(rest_string)
835
            status = self.PARTIAL
836 837
            signature.setAction('Add')
            xml_string = '<!--' + short_string + '-->'
838 839 840
          gid = signature.getRid()#in fisrt, we try with rid if there is one
          if gid == None:
            gid = signature.getGid()
841
          syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object, 
842 843
              gid=gid, xml_string=xml_string, 
              more_data=more_data, media_type=subscriber.getMediaType())
844 845 846 847 848
          cmd_id += 1
          signature.setStatus(status)
          subscriber.addSignature(signature)
        elif signature.getStatus()==self.NOT_SYNCHRONIZED \
            or signature.getStatus()==self.PUB_CONFLICT_MERGE: # We don't have synchronized this object yet
849 850
          xml_object = self.getXMLObject(object=object, 
              xml_mapping=domain.xml_mapping)
851 852
          #LOG('getSyncMLData',0,'checkMD5: %s' % str(signature.checkMD5(xml_object)))
          #LOG('getSyncMLData',0,'getStatus: %s' % str(signature.getStatus()))
853
          if signature.getStatus()==self.PUB_CONFLICT_MERGE:
854 855 856
            xml_confirmation += self.SyncMLConfirmation(cmd_id=cmd_id, 
                source_ref=signature.getGid(), sync_code=self.CONFLICT_MERGE, 
                cmd='Replace')
857
          set_synchronized = 1
858
          if not signature.checkMD5(xml_object):
859
            set_synchronized = 0
860 861 862 863 864
            # This object has changed on this side, we have to generate some xmldiff
            xml_string = self.getXupdateObject(object=object,
                                              xml_mapping=domain.xml_mapping,
                                              old_xml=signature.getXML())
            if xml_string.count('\n') > self.MAX_LINES:
865
              if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
866
                xml_string = xml_string.replace('--','@-@@-@')
867 868 869 870 871 872 873 874 875 876 877 878 879
              i = 0
              more_data=1
              short_string = ''
              rest_string = xml_string
              while i < self.MAX_LINES:
                short_string += rest_string[:rest_string.find('\n')+1]
                rest_string = xml_string[len(short_string):]
                i += 1
              signature.setPartialXML(rest_string)
              status = self.PARTIAL
              signature.setAction('Replace')
              xml_string = '<!--' + short_string + '-->'
            signature.setStatus(status)
880
            if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
881 882
              xml_string = self.getXMLObject(object=object, 
                  xml_mapping=domain.xml_mapping)
883 884 885
            gid = signature.getRid()#in fisrt, we try with rid if there is one
            if gid == None:
              gid = signature.getGid()
886
            syncml_data += self.replaceXMLObject(cmd_id=cmd_id, object=object, 
887 888
                gid=gid, xml_string=xml_string, 
                more_data=more_data, media_type=subscriber.getMediaType())
889 890
            cmd_id += 1
            signature.setTempXML(xml_object)
891 892
          # Now we can apply the xupdate from the subscriber
          subscriber_xupdate = signature.getSubscriberXupdate()
893
          #LOG('getSyncMLData subscriber_xupdate',0,subscriber_xupdate)
894 895
          if subscriber_xupdate is not None:
            old_xml = signature.getXML()
896 897 898 899 900
            conduit.updateNode(xml=subscriber_xupdate, object=object, 
                previous_xml=old_xml, force=(domain.getDomainType==self.SUB), 
                simulate=0)
            xml_object = self.getXMLObject(object=object, 
                xml_mapping=domain.xml_mapping)
901 902 903
            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
904

905 906 907
            signature.setStatus(self.SYNCHRONIZED)
        elif signature.getStatus()==self.PUB_CONFLICT_CLIENT_WIN:
          # We have decided to apply the update
908
          # XXX previous_xml will be geXML instead of getTempXML because
909 910
          # some modification was already made and the update
          # may not apply correctly
911
          xml_update = signature.getPartialXML()
912 913
          conduit.updateNode(xml=signature.getPartialXML(), object=object,
                            previous_xml=signature.getXML(),force=1)
914 915 916
          xml_confirmation += self.SyncMLConfirmation(cmd_id=cmd_id, 
              target_ref=object_gid, sync_code=self.CONFLICT_CLIENT_WIN,
              cmd='Replace')
917 918 919 920 921 922 923 924 925 926 927 928 929 930 931
          signature.setStatus(self.SYNCHRONIZED)
        elif signature.getStatus()==self.PARTIAL:
          xml_string = signature.getPartialXML()
          if xml_string.count('\n') > self.MAX_LINES:
            i = 0
            more_data=1
            short_string = ''
            rest_string = xml_string
            while i < self.MAX_LINES:
              short_string += rest_string[:rest_string.find('\n')+1]
              rest_string = xml_string[len(short_string):]
              i += 1
            signature.setPartialXML(rest_string)
            xml_string = short_string
            status = self.PARTIAL
932
          if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
933
            xml_string = xml_string.replace('--','@-@@-@')
934 935
          xml_string = '<!--' + xml_string + '-->'
          signature.setStatus(status)
936
          if(subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']):
937 938
            xml_string = self.getXMLObject(object=object, 
                xml_mapping=domain.xml_mapping)
Nicolas Delaby's avatar
Nicolas Delaby committed
939
          #LOG('xml_string =', 0, xml_string)
940
          if signature.getAction()=='Replace':
941 942 943
            gid = signature.getRid()#in fisrt, we try with rid if there is one
            if gid == None:
              gid = signature.getGid()
944
            syncml_data += self.replaceXMLObject(cmd_id=cmd_id, object=object,
945
                gid=gid, xml_string=xml_string, more_data=more_data,
946
                media_type=subscriber.getMediaType())
947
          elif signature.getAction()=='Add':
948 949 950
            gid = signature.getRid()#in fisrt, we try with rid if there is one
            if gid == None:
              gid = signature.getGid()
951
            syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object, 
952 953
                gid=gid, xml_string=xml_string, 
                more_data=more_data, media_type=subscriber.getMediaType())
954
    return (syncml_data,xml_confirmation,cmd_id)
955 956

  def applyActionList(self, domain=None, subscriber=None,destination_path=None,
957
                      cmd_id=0,remote_xml=None,conduit=None,simulate=0):
958 959 960
    """
    This just look to a list of action to do, then id applies
    each action one by one, thanks to a conduit
Jean-Paul Smets's avatar
Jean-Paul Smets committed
961
    """
962
    xml_confirmation = ''
Jean-Paul Smets's avatar
Jean-Paul Smets committed
963
    has_next_action = 0
964
    for action in self.getSyncActionList(remote_xml):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
965 966 967
      conflict_list = []
      status_code = self.SUCCESS
      # Thirst we have to check the kind of action it is
968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984
      partial_data = self.getPartialData(action)
      rid = self.getActionId(action, action.nodeName)
      if action.nodeName != 'Delete':
        if hasattr(conduit, 'getGidFromXML'):
          gid = b16encode(conduit.getGidFromXML(self.getDataText(action)))
        else:
          gid=rid
      else:
        gid=rid
      object_id = domain.generateNewIdWithGenerator(object=destination_path,gid=gid)
      signature = subscriber.getSignatureFromGid(gid)
      if signature != None and rid != gid:
        #in this case, the object was created on another subscriber than erp5
        # and we should save it's remote id
        signature.setRid(rid)
      LOG('gid == rid ?', 0, 'gid=%s, rid=%s' % (gid, rid))
      object = subscriber.getObjectFromGid(gid)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
985
      if signature == None:
Nicolas Delaby's avatar
Nicolas Delaby committed
986
        #LOG('applyActionList, signature is None',0,signature)
987 988 989 990 991 992 993
        if gid == rid:
          signature = Signature(id=gid, status=self.NOT_SYNCHRONIZED, 
              object=object).__of__(subscriber)
        else:
          signature = Signature(rid=rid, id=gid, status=self.NOT_SYNCHRONIZED,
              object=object).__of__(subscriber)
        signature.setObjectId(object_id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
994 995
        subscriber.addSignature(signature)
      force = signature.getForce()
Nicolas Delaby's avatar
Nicolas Delaby committed
996
      #LOG('applyActionList',0,'object: %s' % repr(object))
997
      if self.checkActionMoreData(action) == 0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
998 999
        data_subnode = None
        if partial_data != None:
1000 1001 1002 1003 1004
          signature_partial_xml = signature.getPartialXML()
          if signature_partial_xml is not None:
            data_subnode = signature.getPartialXML() + partial_data
          else:
            data_subnode = partial_data
Nicolas Delaby's avatar
Nicolas Delaby committed
1005
          #LOG('SyncModif',0,'data_subnode: %s' % data_subnode)
1006
          if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']:
1007 1008
            data_subnode = Parse(data_subnode)
            data_subnode = data_subnode.childNodes[0] # Because we just created a new xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1009 1010
          # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
        else:
1011
          if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
1012
            data_subnode = self.getDataText(action)
1013
          else:
1014 1015
            data_subnode = self.getDataSubNode(action)
        if action.nodeName == 'Add':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1016
          # Then store the xml of this new subobject
1017
          if object is None:
1018
            object_id = domain.generateNewIdWithGenerator(object=destination_path,gid=gid)
1019
            #if object_id is not None:
1020 1021 1022 1023
            add_data = conduit.addNode(xml=data_subnode, 
                object=destination_path, object_id=object_id)
            if add_data['conflict_list'] not in ('', None, []):
              conflict_list += add_data['conflict_list']
1024 1025
            # Retrieve directly the object from addNode
            object = add_data['object']
1026
            LOG('XMLSyncUtils, in ADD add_data',0,add_data)
Nicolas Delaby's avatar
Nicolas Delaby committed
1027 1028
            if object is not None:
              signature.setPath(object.getPhysicalPath())
1029
              signature.setObjectId(object.getId())
1030 1031 1032
          else:
            #Object was retrieve but need to be updated without recreated
            #usefull when an object is only deleted by workflow.
1033
            object_id = domain.generateNewIdWithGenerator(object=destination_path,gid=gid)
1034 1035 1036 1037
            add_data = conduit.addNode(xml=data_subnode,
                                       object=destination_path,
                                       object_id=object_id,
                                       sub_object=object)
1038 1039
            if add_data['conflict_list'] not in ('', None, []):
              conflict_list += add_data['conflict_list']
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1040
          if object is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
1041
            #LOG('SyncModif',0,'addNode, found the object')
1042 1043 1044 1045
            #mapping = getattr(object,domain.getXMLMapping(),None)
            xml_object = domain.getXMLFromObject(object)
            #if mapping is not None:
            #  xml_object = mapping()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1046
            signature.setStatus(self.SYNCHRONIZED)
1047
            #signature.setId(object.getId())
1048
            signature.setPath(object.getPhysicalPath())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1049
            signature.setXML(xml_object)
1050 1051 1052 1053 1054
            xml_confirmation += self.SyncMLConfirmation(
                cmd_id=cmd_id, 
                cmd='Add', 
                sync_code=self.ITEM_ADDED,
                remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1055
            cmd_id +=1
1056
        elif action.nodeName == 'Replace':
Nicolas Delaby's avatar
Nicolas Delaby committed
1057
          #LOG('SyncModif',0,'object: %s will be updated...' % str(object))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1058
          if object is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
1059
            #LOG('SyncModif',0,'object: %s will be updated...' % object.id)
1060
            signature = subscriber.getSignatureFromGid(gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
1061
            #LOG('SyncModif',0,'previous signature: %s' % str(signature))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1062
            previous_xml = signature.getXML()
1063
            #LOG('SyncModif',0,'previous signature: %i' % len(previous_xml))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1064
            conflict_list += conduit.updateNode(xml=data_subnode, object=object,
1065 1066
                              previous_xml=signature.getXML(),force=force,
                              simulate=simulate)
1067 1068 1069 1070
            #mapping = getattr(object,domain.getXMLMapping(),None)
            xml_object = domain.getXMLFromObject(object)
            #if mapping is not None:
            #  xml_object = mapping()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1071 1072 1073 1074
            signature.setTempXML(xml_object)
            if conflict_list != []:
              status_code = self.CONFLICT
              signature.setStatus(self.CONFLICT)
1075 1076
              signature.setConflictList(signature.getConflictList() \
                  + conflict_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1077 1078 1079 1080
              string_io = StringIO()
              PrettyPrint(data_subnode,stream=string_io)
              data_subnode_string = string_io.getvalue()
              signature.setPartialXML(data_subnode_string)
1081
            elif not simulate:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1082
              signature.setStatus(self.SYNCHRONIZED)
1083 1084 1085 1086 1087
            xml_confirmation += self.SyncMLConfirmation(\
                cmd_id=cmd_id, 
                cmd='Replace', 
                sync_code=status_code,
                remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1088
            cmd_id +=1
1089 1090 1091 1092 1093 1094 1095
            if simulate:
              # This means we are on the publiher side and we want to store
              # the xupdate from the subscriber and we also want to generate
              # the current xupdate from the last synchronization
              string_io = StringIO()
              PrettyPrint(data_subnode,stream=string_io)
              data_subnode_string = string_io.getvalue()
Nicolas Delaby's avatar
Nicolas Delaby committed
1096
              #LOG('applyActionList, subscriber_xupdate:',0,data_subnode_string)
1097 1098
              signature.setSubscriberXupdate(data_subnode_string)

1099
        elif action.nodeName == 'Delete':
1100
          object_id = signature.getId()
1101
          if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']: 
1102
            data_subnode = self.getDataText(action)
1103
          else:
1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
            data_subnode = self.getDataSubNode(action)
          if subscriber.getObjectFromGid(object_id) not in (None, ''):
          #if the object exist:
            conduit.deleteNode(xml=data_subnode, object=destination_path, 
                object_id=subscriber.getObjectFromGid(object_id).getId())
            subscriber.delSignature(gid)
          xml_confirmation += self.SyncMLConfirmation(
              cmd_id=cmd_id, 
              cmd='Delete',
              sync_code=status_code,
              remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1115 1116 1117 1118
      else: # We want to retrieve more data
        signature.setStatus(self.PARTIAL)
        #LOG('SyncModif',0,'setPartialXML: %s' % str(previous_partial))
        previous_partial = signature.getPartialXML() or ''
1119 1120
        #if previous_partial.find(partial_data)<0: # XXX bad thing
        previous_partial += partial_data
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1121
        signature.setPartialXML(previous_partial)
1122
        #LOG('SyncModif',0,'previous_partial: %s' % str(previous_partial))
Nicolas Delaby's avatar
Nicolas Delaby committed
1123
        #LOG('SyncModif',0,'waiting more data for :%s' % signature.getId())
1124 1125 1126 1127 1128 1129 1130
        #xml_confirmation += self.SyncMLConfirmation(cmd_id, object_gid, 
        #    self.WAITING_DATA, action.nodeName)
        xml_confirmation += self.SyncMLConfirmation(\
            cmd_id=cmd_id, 
            cmd=action.nodeName, 
            sync_code=self.WAITING_DATA,
            remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1131 1132 1133 1134
      if conflict_list != [] and signature is not None:
        # We had a conflict
        signature.setStatus(self.CONFLICT)

1135 1136 1137 1138 1139 1140 1141
    return (xml_confirmation,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
    """
1142
    status_list = self.getSyncBodyStatusList(remote_xml)
1143 1144
    has_status_list = 0
    destination_waiting_more_data = 0
1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178
    if status_list != []:
      for status in status_list:
        status_cmd = status['cmd']
        #if status_cmd in ('Delete'):
        #  object_gid = status['target']
        #else:
        object_gid = status['source']
        status_code = int(status['code'])
        if status_cmd in ('Add','Replace'):
          has_status_list = 1
          signature = subscriber.getSignatureFromGid(object_gid)
          if signature == None:
            signature = subscriber.getSignatureFromRid(object_gid)
          if status_code == self.CHUNK_OK:
            destination_waiting_more_data = 1
            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':
          if status_code == self.SUCCESS:
            signature = subscriber.getSignatureFromGid(object_gid)
            if signature == None:
              signature = subscriber.getSignatureFromRid(object_gid)
            subscriber.delSignature(signature.getGid())
1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205
    return (destination_waiting_more_data, has_status_list)


class XMLSyncUtils(XMLSyncUtilsMixin):

  def Sync(self, id, msg=None, RESPONSE=None):
    """
    This is the main method for synchronization
    """
    pass

  def SyncInit(self, domain):
    """
    Initialization of a synchronization, this is
    used for the first message of every synchronization
    """
    pass

  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
    """
1206
    from Products.ERP5SyncML import Conduit
1207
    has_response = 0 #check if syncmodif replies to this messages
1208
    cmd_id = 1 # specifies a SyncML message-unique command identifier
Nicolas Delaby's avatar
Nicolas Delaby committed
1209
    #LOG('SyncModif',0,'Starting... domain: %s' % str(domain))
1210 1211 1212
    # Get the destination folder
    destination_path = self.unrestrictedTraverse(domain.getDestinationPath())

Sebastien Robin's avatar
Sebastien Robin committed
1213
    first_node = remote_xml.childNodes[0]
1214 1215 1216
    # Get informations from the header
    xml_header = first_node.childNodes[1]
    if xml_header.nodeName != "SyncHdr":
Nicolas Delaby's avatar
Nicolas Delaby committed
1217
      #LOG('PubSyncModif',0,'This is not a SyncML Header')
Sebastien Robin's avatar
Sebastien Robin committed
1218
      raise ValueError, "Sorry, This is not a SyncML Header"
1219 1220

    subscriber = domain # If we are the client, this is fine
1221
    simulate = 0 # used by applyActionList, should be 0 for client
1222
    if domain.domain_type == self.PUB:
1223
      simulate = 1
1224
      for subnode in xml_header.childNodes:
1225 1226 1227 1228 1229 1230 1231
        if subnode.nodeType == subnode.ELEMENT_NODE and \
                              subnode.nodeName == "Source":
          for subnode2 in subnode.childNodes:
            if subnode2.nodeType == subnode2.ELEMENT_NODE and \
                                  subnode2.nodeName == 'LocURI':
              subscription_url = str(subnode2.childNodes[0].data)
              subscriber = domain.getSubscriber(subscription_url)
1232

1233 1234
    # We have to check if this message was not already, this can be dangerous
    # to update two times the same object
Sebastien Robin's avatar
Sebastien Robin committed
1235 1236 1237
    message_id = self.getMessageId(remote_xml)
    correct_message = subscriber.checkCorrectRemoteMessageId(message_id)
    if not correct_message: # We need to send again the message
Nicolas Delaby's avatar
Nicolas Delaby committed
1238
      #LOG('SyncModif, no correct message:',0,"sending again...")
1239
      last_xml = subscriber.getLastSentMessage()
Nicolas Delaby's avatar
Nicolas Delaby committed
1240
      #LOG("last_xml :", 0, last_xml)
1241 1242 1243
      if last_xml != '':
        has_response = 1
        if domain.domain_type == self.PUB: # We always reply
1244 1245 1246
          self.sendResponse(from_url=domain.publication_url, 
              to_url=subscriber.subscription_url, sync_id=domain.getTitle(), 
              xml=last_xml,domain=domain)
1247
        elif domain.domain_type == self.SUB:
1248
          self.sendResponse(from_url=domain.subscription_url, 
1249 1250
              to_url=domain.publication_url, sync_id=domain.getTitle(), 
              xml=last_xml, domain=domain)
1251 1252 1253
      return {'has_response':has_response,'xml':last_xml}
    subscriber.setLastSentMessage('')

1254 1255 1256 1257 1258 1259
    # 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.getAlertCode(remote_xml)
Kevin Deldycke's avatar
Kevin Deldycke committed
1260
    # Import the conduit and get it
Nicolas Delaby's avatar
Nicolas Delaby committed
1261
    conduit_name = subscriber.getConduit()
1262 1263 1264 1265 1266 1267
    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:
Nicolas Delaby's avatar
Nicolas Delaby committed
1268
      conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]),
1269 1270
                                  globals(), locals(), [''])
      conduit = getattr(conduit_module, conduit_name)()
1271
    # Then apply the list of actions
1272 1273
    (xml_confirmation,has_next_action,cmd_id) = self.applyActionList(
                                         cmd_id=cmd_id,
1274 1275 1276 1277
                                         domain=domain,
                                         destination_path=destination_path,
                                         subscriber=subscriber,
                                         remote_xml=remote_xml,
1278
                                         conduit=conduit, simulate=simulate)
1279
    #LOG('SyncModif, has_next_action:',0,has_next_action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1280

Sebastien Robin's avatar
Sebastien Robin committed
1281 1282 1283
    xml_list = []
    xml = xml_list.append
    xml('<SyncML>\n')
1284

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1285 1286
    # syncml header
    if domain.domain_type == self.PUB:
1287 1288 1289
      xml(self.SyncMLHeader(subscriber.getSessionId(), 
        subscriber.incrementMessageId(), subscriber.getSubscriptionUrl(), 
        domain.getPublicationUrl()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1290
    elif domain.domain_type == self.SUB:
Sebastien Robin's avatar
Sebastien Robin committed
1291 1292
      xml(self.SyncMLHeader(domain.getSessionId(), domain.incrementMessageId(),
        domain.getPublicationUrl(), domain.getSubscriptionUrl()))
1293
    
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1294 1295 1296 1297
    # Add or replace objects
    syncml_data = ''

    # syncml body
Sebastien Robin's avatar
Sebastien Robin committed
1298
    xml(' <SyncBody>\n')
1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355

    # status for SyncHdr
    message_id = self.getMessageId(remote_xml)
    xml('  <Status>\n')
    xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    cmd_id += 1
    xml('   <MsgRef>%s</MsgRef>\n' % message_id)
    xml('   <CmdRef>0</CmdRef>\n') #to make reference to the SyncHdr, it's 0
    xml('   <Cmd>SyncHdr</Cmd>\n')
    xml('   <TargetRef>%s</TargetRef>\n' \
      % remote_xml.xpath('string(//SyncHdr/Target/LocURI)').encode('utf-8'))
    xml('   <SourceRef>%s</SourceRef>\n' \
      % remote_xml.xpath('string(//SyncHdr/Source/LocURI)').encode('utf-8'))
    xml('   <Data>200</Data>\n')
    xml('  </Status>\n')

    #list of element in the SyncBody bloc
    syncbody_element_list = remote_xml.xpath('//SyncBody/*')
    
    #add the status bloc corresponding to the receive command
    for syncbody_element in syncbody_element_list:
      if str(syncbody_element.nodeName) not in ('Status', 'Final', 'Replace'):
        xml('  <Status>\n')
        xml('   <CmdID>%s</CmdID>\n' % cmd_id)
        cmd_id += 1
        xml('   <MsgRef>%s</MsgRef>\n' % message_id)
        xml('   <CmdRef>%s</CmdRef>\n' \
            % syncbody_element.xpath('string(.//CmdID)').encode('utf-8'))
        xml('   <Cmd>%s</Cmd>\n' % syncbody_element.nodeName.encode('utf-8'))

        target_ref = syncbody_element.xpath('string(.//Target/LocURI)').encode('utf-8')
        if target_ref not in (None, ''):
          xml('   <TargetRef>%s</TargetRef>\n' % target_ref )
        source_ref = syncbody_element.xpath('string(.//Source/LocURI)').encode('utf-8')
        if source_ref not in (None, ''):
          xml('   <SourceRef>%s</SourceRef>\n' % source_ref )

        #xml('   <Data>%s</Data>\n' % subscriber.getSynchronizationType())
        if syncbody_element.nodeName.encode('utf-8') == 'Add':
          xml('   <Data>%s</Data>\n' % '201')
        else:
          xml('   <Data>%s</Data>\n' % '200')

       # if syncbody_element.xpath('.//Item') not in ([], None, '') and\
       #     syncbody_element.xpath('.//Item.....'): #contient une ancre Next...

        xml('   <Item>\n')
        xml('    <Data>\n')
        xml("     <Anchor xmlns='syncml:metinf'>\n")
        xml('      <Next>%s</Next>\n' % subscriber.getNextAnchor())
        xml('     </Anchor>\n')
        xml('    </Data>\n')
        xml('   </Item>\n')

        xml('  </Status>\n')
        

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1356 1357 1358
    destination_url = ''
    # alert message if we want more data
    if destination_waiting_more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
1359
      xml(self.SyncMLAlert(cmd_id, self.WAITING_DATA,
1360 1361
                              subscriber.getTargetURI(),
                              subscriber.getSourceURI(),
Sebastien Robin's avatar
Sebastien Robin committed
1362 1363
                              subscriber.getLastAnchor(), 
                              subscriber.getNextAnchor()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1364
    # Now we should send confirmations
1365 1366 1367 1368 1369 1370 1371
    cmd_id_before_getsyncmldata = cmd_id
    (syncml_data,xml_confirmation,cmd_id) = self.getSyncMLData(domain=domain,
                             remote_xml=remote_xml,
                             subscriber=subscriber,
                             destination_path=destination_path,
                             cmd_id=cmd_id+1,xml_confirmation=xml_confirmation,
                             conduit=conduit)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1372
    if syncml_data != '':
Sebastien Robin's avatar
Sebastien Robin committed
1373
      xml('  <Sync>\n')
1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392
      xml('   <CmdID>%s</CmdID>\n' % cmd_id_before_getsyncmldata)
      if domain.domain_type == self.SUB:
        if subscriber.getTargetURI() not in ('', None):
          xml('   <Target>\n')
          xml('    <LocURI>%s</LocURI>\n' % subscriber.getTargetURI())
          xml('   </Target>\n')
        if subscriber.getSourceURI() not in ('', None):
          xml('   <Source>\n')
          xml('    <LocURI>%s</LocURI>\n' % subscriber.getSourceURI())
          xml('   </Source>\n')
      elif domain.domain_type == self.PUB:
        if domain.getTargetURI() not in ('', None):
          xml('   <Target>\n')
          xml('    <LocURI>%s</LocURI>\n' % domain.getTargetURI())
          xml('   </Target>\n')
        if domain.getSourceURI() not in ('', None):
          xml('   <Source>\n')
          xml('    <LocURI>%s</LocURI>\n' % domain.getSourceURI())
          xml('   </Source>\n')
Sebastien Robin's avatar
Sebastien Robin committed
1393 1394
      xml(syncml_data)
      xml('  </Sync>\n')
1395
    xml(xml_confirmation)
Sebastien Robin's avatar
Sebastien Robin committed
1396 1397 1398 1399
    xml('  <Final/>\n')
    xml(' </SyncBody>\n')
    xml('</SyncML>\n')
    xml_a = ''.join(xml_list)
1400

1401
    if domain.domain_type == self.PUB: # We always reply
Sebastien Robin's avatar
Sebastien Robin committed
1402 1403 1404 1405
      subscriber.setLastSentMessage(xml_a)
      self.sendResponse(from_url=domain.publication_url, 
          to_url=subscriber.subscription_url, sync_id=domain.getTitle(), 
          xml=xml_a,domain=domain)
1406 1407 1408
      has_response = 1
    elif domain.domain_type == self.SUB:
      if self.checkAlert(remote_xml) or \
1409
          (xml_confirmation,syncml_data)!=('','') or \
1410
          has_status_list:
Sebastien Robin's avatar
Sebastien Robin committed
1411 1412 1413 1414
        subscriber.setLastSentMessage(xml_a)
        self.sendResponse(from_url=domain.subscription_url, 
            to_url=domain.publication_url, sync_id=domain.getTitle(), 
            xml=xml_a,domain=domain)
1415
        has_response = 1
Sebastien Robin's avatar
Sebastien Robin committed
1416
    return {'has_response':has_response,'xml':xml_a}