Package muntjac :: Package terminal :: Package gwt :: Package server :: Module json_paint_target
[hide private]
[frames] | no frames]

Source Code for Module muntjac.terminal.gwt.server.json_paint_target

  1  # Copyright (C) 2012 Vaadin Ltd.  
  2  # Copyright (C) 2012 Richard Lincoln 
  3  #  
  4  # Licensed under the Apache License, Version 2.0 (the "License");  
  5  # you may not use this file except in compliance with the License.  
  6  # You may obtain a copy of the License at  
  7  #  
  8  #     http://www.apache.org/licenses/LICENSE-2.0  
  9  #  
 10  # Unless required by applicable law or agreed to in writing, software  
 11  # distributed under the License is distributed on an "AS IS" BASIS,  
 12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
 13  # See the License for the specific language governing permissions and  
 14  # limitations under the License. 
 15   
 16  """UIDL target""" 
 17   
 18  import logging 
 19   
 20  from warnings import warn 
 21   
 22  from collections import deque 
 23  from muntjac.util import getSuperClass 
 24  from muntjac.util import clsname 
 25   
 26  try: 
 27      from cStringIO import StringIO 
 28  except ImportError, e: 
 29      from StringIO import StringIO 
 30   
 31  from muntjac.ui.custom_layout import CustomLayout 
 32  from muntjac.terminal.resource import IResource 
 33  from muntjac.terminal.external_resource import ExternalResource 
 34  from muntjac.terminal.application_resource import IApplicationResource 
 35  from muntjac.terminal.theme_resource import ThemeResource 
 36  from muntjac.ui.alignment import Alignment 
 37  from muntjac.terminal.stream_variable import IStreamVariable 
 38  from muntjac.terminal.paintable import IPaintable, IRepaintRequestListener 
 39  from muntjac.terminal.paint_target import IPaintTarget 
 40  from muntjac.terminal.paint_exception import PaintException 
 41   
 42   
 43  logger = logging.getLogger(__name__) 
44 45 46 -class JsonPaintTarget(IPaintTarget):
47 """User Interface Description Language Target. 48 49 TODO: document better: role of this class, UIDL format, 50 attributes, variables, etc. 51 52 @author: Vaadin Ltd. 53 @author: Richard Lincoln 54 @version: 1.1.2 55 """ 56 57 _UIDL_ARG_NAME = 'name' 58
59 - def __init__(self, manager, outWriter, cachingRequired):
60 """Creates a new XMLPrintWriter, without automatic line flushing. 61 62 @param manager: 63 @param outWriter: 64 A character-output stream. 65 @param cachingRequired: 66 True if this is not a full repaint, i.e. caches are to 67 be used. 68 @raise PaintException: 69 if the paint operation failed. 70 """ 71 self._manager = manager 72 73 # Sets the target for UIDL writing 74 self._uidlBuffer = outWriter 75 76 # Initialize tag-writing 77 self._mOpenTags = deque() 78 79 self._openJsonTags = deque() 80 81 self._cacheEnabled = cachingRequired 82 83 self._closed = False 84 self._changes = 0 85 self._usedResources = set() 86 self._customLayoutArgumentsOpen = False 87 self._tag = None 88 self._errorsOpen = 0 89 self._paintedComponents = set() 90 self._identifiersCreatedDueRefPaint = None 91 self._usedPaintableTypes = list()
92 93
94 - def startTag(self, arg, arg2=False):
95 """Prints the element start tag. 96 97 @param arg: 98 paintable or the name of the start tag 99 @param arg2: 100 the name of the start tag for the given paintable 101 @raise PaintException: 102 if the paint operation failed. 103 """ 104 if isinstance(arg, IPaintable): 105 paintable, tagName = arg, arg2 106 self.startTag(tagName, True) 107 isPreviouslyPainted = (self._manager.hasPaintableId(paintable) 108 and (self._identifiersCreatedDueRefPaint is None 109 or paintable not in self._identifiersCreatedDueRefPaint)) 110 idd = self._manager.getPaintableId(paintable) 111 paintable.addListener(self._manager, IRepaintRequestListener) 112 self.addAttribute('id', idd) 113 self._paintedComponents.add(paintable) 114 115 if isinstance(paintable, CustomLayout): 116 self._customLayoutArgumentsOpen = True 117 118 return self._cacheEnabled and isPreviouslyPainted 119 else: 120 tagName, _ = arg, arg2 121 if tagName is None: 122 raise ValueError 123 124 # Ensures that the target is open 125 if self._closed: 126 raise PaintException, \ 127 'Attempted to write to a closed IPaintTarget.' 128 129 if self._tag is not None: 130 self._openJsonTags.append(self._tag) 131 132 # Checks tagName and attributes here 133 self._mOpenTags.append(tagName) 134 135 self._tag = JsonTag(tagName, self) 136 137 if 'error' == tagName: 138 self._errorsOpen += 1 139 140 self._customLayoutArgumentsOpen = False
141 142
143 - def endTag(self, tagName):
144 """Prints the element end tag. 145 146 If the parent tag is closed before every child tag is closed an 147 PaintException is raised. 148 149 @param tagName: 150 the name of the end tag. 151 @raise Paintexception: 152 if the paint operation failed. 153 """ 154 # In case of null data output nothing: 155 if tagName is None: 156 raise ValueError 157 158 # Ensure that the target is open 159 if self._closed: 160 raise PaintException, 'Attempted to write to a closed IPaintTarget.' 161 162 if len(self._openJsonTags) > 0: 163 parent = self._openJsonTags.pop() 164 165 lastTag = '' 166 167 lastTag = self._mOpenTags.pop() 168 if tagName.lower() != lastTag.lower(): 169 raise PaintException, ('Invalid UIDL: wrong ending tag: \'' 170 + tagName + '\' expected: \'' + lastTag + '\'.') 171 172 # simple hack which writes error uidl structure into attribute 173 if 'error' == lastTag: 174 if self._errorsOpen == 1: 175 parent.addAttribute(('\"error\":[\"error\",{}' 176 + self._tag.getData() + ']')) 177 else: 178 # sub error 179 parent.addData(self._tag.getJSON()) 180 181 self._errorsOpen -= 1 182 else: 183 parent.addData(self._tag.getJSON()) 184 185 self._tag = parent 186 else: 187 self._changes += 1 188 self._uidlBuffer.write((',' if self._changes > 1 else '') 189 + self._tag.getJSON()) 190 self._tag = None
191 192 193 @classmethod
194 - def escapeXML(cls, xml):
195 """Substitutes the XML sensitive characters with predefined XML 196 entities. 197 198 @param xml: 199 the string to be substituted. 200 @return: A new string instance where all occurrences of XML 201 sensitive characters are substituted with entities. 202 """ 203 if xml is None or len(xml) <= 0: 204 return '' 205 206 return cls._escapeXML(xml)
207 208 209 @classmethod
210 - def _escapeXML(cls, xml):
211 """Substitutes the XML sensitive characters with predefined XML 212 entities. 213 214 @param xml: 215 the string to be substituted. 216 @return: A new StringBuilder instance where all occurrences of XML 217 sensitive characters are substituted with entities. 218 """ 219 if xml is None or len(xml) <= 0: 220 return '' 221 222 buff = StringIO() #(len(xml) * 2) 223 224 for c in xml: 225 s = cls.toXmlChar(c) 226 if s is not None: 227 buff.write(s) 228 else: 229 buff.write(c) 230 231 result = buff.getvalue() 232 buff.close() 233 return result
234 235 236 @staticmethod
237 - def _default(ch, sb):
238 if ch >= '\u0000' and ch <= '\u001F': 239 ss = hex( int(ch) ) 240 sb.write('\\u') 241 for _ in range(4 - len(ss)): 242 sb.write('0') 243 sb.write( ss.upper() ) 244 else: 245 sb.write(ch)
246 247 248 _json_map = { 249 '"': lambda ch, sb: sb.write('\\\"'), 250 '\\': lambda ch, sb: sb.write('\\\\'), 251 '\b': lambda ch, sb: sb.write('\\b'), 252 '\f': lambda ch, sb: sb.write('\\f'), 253 '\n': lambda ch, sb: sb.write('\\n'), 254 '\r': lambda ch, sb: sb.write('\\r'), 255 '\t': lambda ch, sb: sb.write('\\t'), 256 '/' : lambda ch, sb: sb.write('\\/') 257 } 258 259 260 @classmethod
261 - def escapeJSON(cls, s):
262 """Escapes the given string so it can safely be used as a JSON string. 263 264 @param s: The string to escape 265 @return: Escaped version of the string 266 """ 267 # FIXME: Move this method to another class as other classes use it 268 # also. 269 if s is None: 270 return '' 271 272 sb = StringIO() 273 274 for c in s: 275 cls._json_map.get(c, cls._default)(c, sb) 276 277 result = sb.getvalue() 278 sb.close() 279 return result
280 281 282 @classmethod
283 - def toXmlChar(cls, c):
284 """Substitutes a XML sensitive character with predefined XML entity. 285 286 @param c: 287 the character to be replaced with an entity. 288 @return: String of the entity or null if character is not to be 289 replaced with an entity. 290 """ 291 return { 292 '&': '&amp;', # & => &amp; 293 '>': '&gt;', # > => &gt; 294 '<': '&lt;', # < => &lt; 295 '"': '&quot;', # " => &quot; 296 "'": '&apos;' # ' => &apos; 297 }.get(c)
298 299
300 - def addText(self, s):
301 """Prints XML-escaped text. 302 303 @raise PaintException: 304 if the paint operation failed. 305 """ 306 self._tag.addData('\"' + self.escapeJSON(s) + '\"')
307 308
309 - def addAttribute(self, name, value):
310 if isinstance(value, list): 311 values = value 312 # In case of null data output nothing: 313 if values is None or name is None: 314 raise ValueError, 'Parameters must be non-null strings' 315 buf = StringIO() 316 buf.write('\"' + name + '\":[') 317 for i in range(len(values)): 318 if i > 0: 319 buf.write(',') 320 buf.write('\"') 321 buf.write(self.escapeJSON( str(values[i]) )) 322 buf.write('\"') 323 buf.write(']') 324 self._tag.addAttribute(buf.getvalue()) 325 buf.close() 326 elif isinstance(value, IPaintable): 327 idd = self.getPaintIdentifier(value) 328 self.addAttribute(name, idd) 329 elif isinstance(value, IResource): 330 331 if isinstance(value, ExternalResource): 332 self.addAttribute(name, value.getURL()) 333 elif isinstance(value, IApplicationResource): 334 r = value 335 a = r.getApplication() 336 if a is None: 337 raise PaintException, ('Application not specified ' 338 'for resource ' + value.__class__.__name__) 339 uri = a.getRelativeLocation(r) 340 self.addAttribute(name, uri) 341 elif isinstance(value, ThemeResource): 342 uri = 'theme://' + value.getResourceId() 343 self.addAttribute(name, uri) 344 else: 345 raise PaintException, ('Ajax adapter does not support ' 346 'resources of type: ' + value.__class__.__name__) 347 elif isinstance(value, bool): 348 self._tag.addAttribute(('\"' + name + '\":' 349 + ('true' if value else 'false'))) 350 elif isinstance(value, dict): 351 sb = StringIO() 352 sb.write('\"') 353 sb.write(name) 354 sb.write('\": ') 355 sb.write('{') 356 i = 0 357 for key, mapValue in value.iteritems(): 358 sb.write('\"') 359 if isinstance(key, IPaintable): 360 paintable = key 361 sb.write( self.getPaintIdentifier(paintable) ) 362 else: 363 sb.write( self.escapeJSON(str(key)) ) 364 sb.write('\":') 365 if isinstance(mapValue, (float, int, float, bool, Alignment)): 366 sb.write( str(mapValue) ) 367 else: 368 sb.write('\"') 369 sb.write( self.escapeJSON(str(mapValue)) ) 370 sb.write('\"') 371 if i < len(value) - 1: 372 sb.write(',') 373 i += 1 374 sb.write('}') 375 self._tag.addAttribute(sb.getvalue()) 376 sb.close() 377 elif isinstance(value, str): 378 # In case of null data output nothing: 379 if (value is None) or (name is None): 380 raise ValueError, 'Parameters must be non-null strings' 381 382 self._tag.addAttribute(('\"' + name + '\": \"' 383 + self.escapeJSON(value) + '\"')) 384 385 if self._customLayoutArgumentsOpen and 'template' == name: 386 self.getUsedResources().add('layouts/' + value + '.html') 387 388 if name == 'locale': 389 self._manager.requireLocale(value) 390 else: 391 self._tag.addAttribute('\"' + name + '\":' + str(value))
392 393
394 - def addVariable(self, owner, name, value):
395 if value is None: 396 value = '' 397 398 if isinstance(value, IPaintable): 399 var = StringVariable(owner, name, self.getPaintIdentifier(value)) 400 self._tag.addVariable(var) 401 elif isinstance(value, IStreamVariable): 402 url = self._manager.getStreamVariableTargetUrl(owner, name, value) 403 if url is not None: 404 self.addVariable(owner, name, url) 405 # else { //NOP this was just a cleanup by component } 406 elif isinstance(value, bool): 407 self._tag.addVariable( BooleanVariable(owner, name, value) ) 408 elif isinstance(value, float): 409 self._tag.addVariable( DoubleVariable(owner, name, value) ) 410 #elif isinstance(value, float): 411 # self._tag.addVariable( FloatVariable(owner, name, value) ) 412 elif isinstance(value, int): 413 self._tag.addVariable( IntVariable(owner, name, value) ) 414 elif isinstance(value, long): 415 self._tag.addVariable( LongVariable(owner, name, value) ) 416 elif isinstance(value, basestring): 417 var = StringVariable(owner, name, self.escapeJSON(value)) 418 self._tag.addVariable(var) 419 elif isinstance(value, list): 420 self._tag.addVariable( ArrayVariable(owner, name, value) ) 421 else: 422 raise ValueError, ('%s %s %s' % (str(owner), name, value))
423 424
425 - def addUploadStreamVariable(self, owner, name):
426 """Adds a upload stream type variable. 427 428 @param owner: 429 the Listener for variable changes. 430 @param name: 431 the Variable name. 432 @raise PaintException: 433 if the paint operation failed. 434 """ 435 self.startTag('uploadstream') 436 self.addAttribute(self._UIDL_ARG_NAME, name) 437 self.endTag('uploadstream')
438 439
440 - def addSection(self, sectionTagName, sectionData):
441 """Prints the single text section. 442 443 Prints full text section. The section data is escaped 444 445 @param sectionTagName: 446 the name of the tag. 447 @param sectionData: 448 the section data to be printed. 449 @raise PaintException: 450 if the paint operation failed. 451 """ 452 self._tag.addData(('{\"' + sectionTagName + '\":\"' 453 + self.escapeJSON(sectionData) + '\"}'))
454 455
456 - def addUIDL(self, xml):
457 """Adds XML directly to UIDL. 458 459 @param xml: 460 the Xml to be added. 461 @raise PaintException: 462 if the paint operation failed. 463 """ 464 # Ensure that the target is open 465 if self._closed: 466 raise PaintException, 'Attempted to write to a closed IPaintTarget.' 467 468 # Make sure that the open start tag is closed before 469 # anything is written. 470 471 # Escape and write what was given 472 if xml is not None: 473 self._tag.addData('\"' + self.escapeJSON(xml) + '\"')
474 475
476 - def addXMLSection(self, sectionTagName, sectionData, namespace):
477 """Adds XML section with namespace. 478 479 @param sectionTagName: 480 the name of the tag. 481 @param sectionData: 482 the section data. 483 @param namespace: 484 the namespace to be added. 485 @raise PaintException: 486 if the paint operation failed. 487 @see: L{IPaintTarget.addXMLSection} 488 """ 489 # Ensure that the target is open 490 if self._closed: 491 raise PaintException, 'Attempted to write to a closed IPaintTarget.' 492 493 self.startTag(sectionTagName) 494 495 if namespace is not None: 496 self.addAttribute('xmlns', namespace) 497 498 if sectionData is not None: 499 self._tag.addData('\"' + self.escapeJSON(sectionData) + '\"') 500 501 self.endTag(sectionTagName)
502 503
504 - def getUIDL(self):
505 """Gets the UIDL already printed to stream. Paint target must be 506 closed before the C{getUIDL} can be called. 507 508 @return: the UIDL. 509 """ 510 if self._closed: 511 return self._uidlBuffer.getvalue() 512 513 raise ValueError, 'Tried to read UIDL from open IPaintTarget'
514 515
516 - def close(self):
517 """Closes the paint target. Paint target must be closed before the 518 C{getUIDL} can be called. Subsequent attempts to write to paint 519 target. If the target was already closed, call to this function 520 is ignored. will generate an exception. 521 522 @raise PaintException: 523 if the paint operation failed. 524 """ 525 if self._tag is not None: 526 self._uidlBuffer.write(self._tag.getJSON()) 527 self.flush() 528 self._closed = True
529 530
531 - def flush(self):
532 """Method flush."""
533 #self._uidlBuffer.flush() # don't flush HTTPResponse 534 535
536 - def paintReference(self, paintable, referenceName):
537 warn("deprecated", DeprecationWarning) 538 self.addAttribute(referenceName, paintable)
539 540
541 - def getPaintIdentifier(self, paintable):
542 if not self._manager.hasPaintableId(paintable): 543 if self._identifiersCreatedDueRefPaint is None: 544 self._identifiersCreatedDueRefPaint = set() 545 self._identifiersCreatedDueRefPaint.add(paintable) 546 547 return self._manager.getPaintableId(paintable)
548 549
550 - def addCharacterData(self, text):
551 if text is not None: 552 self._tag.addData(text)
553 554
555 - def getUsedResources(self):
556 return self._usedResources
557 558
559 - def needsToBePainted(self, p):
560 """Method to check if paintable is already painted into this target. 561 562 @return: true if is not yet painted into this target and is connected 563 to app 564 """ 565 if p in self._paintedComponents: 566 return False 567 elif p.getApplication() is None: 568 return False 569 else: 570 return True
571 572 573 _widgetMappingCache = dict() 574 575
576 - def getTag(self, paintable):
577 class1 = self._widgetMappingCache.get(paintable.__class__) 578 579 if class1 is None: 580 # Client widget annotation is searched from component hierarchy 581 # to detect the component that presumably has client side 582 # implementation. The server side name is used in the 583 # transportation, but encoded into integer strings to optimized 584 # transferred data. 585 class1 = paintable.__class__ 586 while not self.hasClientWidgetMapping(class1): 587 superclass = getSuperClass(class1) 588 if superclass is not None and issubclass(class1, IPaintable): 589 class1 = superclass 590 else: 591 logger.warning(('No superclass of ' 592 + clsname(paintable.__class__) 593 + ' has a @ClientWidget annotation. Component ' 594 'will not be mapped correctly on client side.')) 595 break 596 597 self._widgetMappingCache[paintable.__class__] = class1 598 599 self._usedPaintableTypes.append(class1) 600 return self._manager.getTagForType(class1)
601 602
603 - def hasClientWidgetMapping(self, class1):
604 return 'CLIENT_WIDGET' in class1.__dict__
605 606
607 - def getUsedPaintableTypes(self):
608 return self._usedPaintableTypes
609 610
611 - def isFullRepaint(self):
612 """@see L{PaintTarget.isFullRepaint}""" 613 return not self._cacheEnabled
614
615 616 -class JsonTag(object):
617 """This is basically a container for UI components variables, that 618 will be added at the end of JSON object. 619 620 @author: mattitahvonen 621 """ 622
623 - def __init__(self, tagName, target):
624 625 self._firstField = False 626 self._variables = list() 627 self._children = list() 628 self._attr = list() 629 self._data = StringIO() # FIXME ensure close 630 self.childrenArrayOpen = False 631 self._childNode = False 632 self._tagClosed = False 633 634 self._data.write('[\"' + tagName + '\"') 635 636 self._target = target
637 638
639 - def _closeTag(self):
640 if not self._tagClosed: 641 self._data.write(self._attributesAsJsonObject()) 642 self._data.write(self.getData()) 643 # Writes the end (closing) tag 644 self._data.write(']') 645 self._tagClosed = True
646 647
648 - def getJSON(self):
649 if not self._tagClosed: 650 self._closeTag() 651 return self._data.getvalue()
652 653
654 - def openChildrenArray(self):
655 if not self.childrenArrayOpen: 656 # append("c : ["); 657 self.childrenArrayOpen = True
658 # firstField = true; 659 660
661 - def closeChildrenArray(self):
662 # append("]"); 663 # firstField = false; 664 pass
665 666
667 - def setChildNode(self, b):
668 self._childNode = b
669 670
671 - def isChildNode(self):
672 return self._childNode
673 674
675 - def startField(self):
676 if self._firstField: 677 self._firstField = False 678 return '' 679 else: 680 return ','
681 682
683 - def addData(self, s):
684 """@param s: json string, object or array""" 685 self._children.append(s)
686 687
688 - def getData(self):
689 buf = StringIO() 690 for c in self._children: 691 buf.write(self.startField()) 692 buf.write(c) 693 result = buf.getvalue() 694 buf.close() 695 return result
696 697
698 - def addAttribute(self, jsonNode):
699 self._attr.append(jsonNode)
700 701
702 - def _attributesAsJsonObject(self):
703 buf = StringIO() 704 buf.write(self.startField()) 705 buf.write('{') 706 for i, element in enumerate(self._attr): 707 buf.write(element) 708 if i != len(self._attr) - 1: 709 buf.write(',') 710 buf.write(self._target._tag._variablesAsJsonObject()) 711 buf.write('}') 712 result = buf.getvalue() 713 buf.close() 714 return result
715 716
717 - def addVariable(self, v):
718 self._variables.append(v)
719 720
721 - def _variablesAsJsonObject(self):
722 if len(self._variables) == 0: 723 return '' 724 725 buf = StringIO() 726 buf.write(self.startField()) 727 buf.write('\"v\":{') 728 for i, element in enumerate(self._variables): 729 buf.write(element.getJsonPresentation()) 730 if i != len(self._variables) - 1: 731 buf.write(',') 732 buf.write('}') 733 result = buf.getvalue() 734 buf.close() 735 return result
736
737 738 -class Variable(object):
739
740 - def getJsonPresentation(self):
741 pass
742
743 744 -class BooleanVariable(Variable):
745
746 - def __init__(self, owner, name, v):
747 self._value = v 748 self.name = name
749 750
751 - def getJsonPresentation(self):
752 return '\"' + self.name + '\":' \ 753 + ('true' if self._value == True else 'false')
754
755 756 -class StringVariable(Variable):
757
758 - def __init__(self, owner, name, v):
759 self._value = v 760 self.name = name
761 762
763 - def getJsonPresentation(self):
764 return '\"' + self.name + '\":\"' + self._value + '\"'
765
766 767 -class IntVariable(Variable):
768
769 - def __init__(self, owner, name, v):
770 self._value = v 771 self.name = name
772 773
774 - def getJsonPresentation(self):
775 return '\"' + self.name + '\":' + str(self._value)
776
777 778 -class LongVariable(Variable):
779
780 - def __init__(self, owner, name, v):
781 self._value = v 782 self.name = name
783 784
785 - def getJsonPresentation(self):
786 return '\"' + self.name + '\":' + str(self._value)
787
788 789 -class FloatVariable(Variable):
790
791 - def __init__(self, owner, name, v):
792 self._value = v 793 self.name = name
794 795
796 - def getJsonPresentation(self):
797 return '\"' + self.name + '\":' + str(self._value)
798
799 800 -class DoubleVariable(Variable):
801
802 - def __init__(self, owner, name, v):
803 self._value = v 804 self.name = name
805 806
807 - def getJsonPresentation(self):
808 return '\"' + self.name + '\":' + str(self._value)
809
810 811 -class ArrayVariable(Variable):
812
813 - def __init__(self, owner, name, v):
814 self._value = v 815 self.name = name
816 817
818 - def getJsonPresentation(self):
819 sb = StringIO() 820 sb.write('\"') 821 sb.write(self.name) 822 sb.write('\":[') 823 for i in range(len(self._value)): 824 sb.write('\"') 825 sb.write(JsonPaintTarget.escapeJSON( str(self._value[i]) )) 826 sb.write('\"') 827 if i < len(self._value) - 1: 828 sb.write(',') 829 sb.write(']') 830 result = sb.getvalue() 831 sb.close() 832 return result
833