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

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

   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  """Defines a common base class for the server-side implementations of 
  17  the communication system between the client code and the server side 
  18  components.""" 
  19   
  20  import re 
  21  import uuid 
  22  import logging 
  23   
  24  from warnings import warn 
  25   
  26  from sys import stderr 
  27  from urlparse import urljoin 
  28   
  29  try: 
  30      from cStringIO import StringIO as cStringIO 
  31      from StringIO import StringIO 
  32  except ImportError, e: 
  33      from StringIO import StringIO as cStringIO 
  34      from StringIO import StringIO 
  35   
  36  from babel import Locale 
  37   
  38  from muntjac.util import clsname 
  39   
  40  from muntjac.terminal.gwt.server.json_paint_target import JsonPaintTarget 
  41  from muntjac.terminal.gwt.server.exceptions import UploadException 
  42  from muntjac.terminal.paintable import IPaintable, IRepaintRequestListener 
  43  from muntjac.terminal.terminal import IErrorEvent as TerminalErrorEvent 
  44  from muntjac.terminal.uri_handler import IErrorEvent as URIHandlerErrorEvent 
  45   
  46  from muntjac.ui.abstract_component import AbstractComponent 
  47  from muntjac.ui.window import Window 
  48  from muntjac.ui.component import IComponent 
  49  from muntjac.ui.abstract_field import AbstractField 
  50   
  51  from muntjac.terminal.gwt.server.streaming_events import \ 
  52      StreamingStartEventImpl, StreamingErrorEventImpl, StreamingEndEventImpl 
  53   
  54  from muntjac.terminal.gwt.server.drag_and_drop_service import \ 
  55      DragAndDropService 
  56   
  57  from muntjac.terminal.gwt.client.application_connection import \ 
  58      ApplicationConnection 
  59   
  60  from muntjac.terminal.gwt.server.exceptions import \ 
  61      NoInputStreamException, NoOutputStreamException 
  62   
  63  from muntjac.terminal.gwt.server.abstract_application_servlet import \ 
  64      AbstractApplicationServlet, URIHandlerErrorImpl 
  65   
  66  from muntjac.terminal.gwt.server.change_variables_error_event import \ 
  67      ChangeVariablesErrorEvent 
  68   
  69  from muntjac.terminal.gwt.server.streaming_events import \ 
  70      StreamingProgressEventImpl 
  71   
  72   
  73  logger = logging.getLogger(__file__) 
74 75 76 -class AbstractCommunicationManager(IPaintable, IRepaintRequestListener):
77 """This is a common base class for the server-side implementations of 78 the communication system between the client code (compiled with GWT 79 into JavaScript) and the server side components. Its client side 80 counterpart is L{ApplicationConnection}. 81 82 A server side component sends its state to the client in a paint request 83 (see L{IPaintable} and L{PaintTarget} on the server side). The 84 client widget receives these paint requests as calls to 85 L{muntjac.terminal.gwt.client.IPaintable.updateFromUIDL}. The 86 client component communicates back to the server by sending a list of 87 variable changes (see L{ApplicationConnection.updateVariable} and 88 L{VariableOwner.changeVariables}). 89 """ 90 91 _DASHDASH = '--' 92 93 _GET_PARAM_REPAINT_ALL = 'repaintAll' 94 95 # flag used in the request to indicate that the security token should be 96 # written to the response 97 _WRITE_SECURITY_TOKEN_FLAG = 'writeSecurityToken' 98 99 # Variable records indexes 100 _VAR_PID = 1 101 _VAR_NAME = 2 102 _VAR_TYPE = 3 103 _VAR_VALUE = 0 104 105 _VTYPE_PAINTABLE = 'p' 106 _VTYPE_BOOLEAN = 'b' 107 _VTYPE_DOUBLE = 'd' 108 _VTYPE_FLOAT = 'f' 109 _VTYPE_LONG = 'l' 110 _VTYPE_INTEGER = 'i' 111 _VTYPE_STRING = 's' 112 _VTYPE_ARRAY = 'a' 113 _VTYPE_STRINGARRAY = 'c' 114 _VTYPE_MAP = 'm' 115 116 _VAR_RECORD_SEPARATOR = u'\u001e' 117 118 _VAR_FIELD_SEPARATOR = u'\u001f' 119 120 VAR_BURST_SEPARATOR = u'\u001d' 121 122 VAR_ARRAYITEM_SEPARATOR = u'\u001c' 123 124 VAR_ESCAPE_CHARACTER = u'\u001b' 125 126 _MAX_BUFFER_SIZE = 64 * 1024 127 128 # Same as in apache commons file upload library that was previously used. 129 _MAX_UPLOAD_BUFFER_SIZE = 4 * 1024 130 131 _GET_PARAM_ANALYZE_LAYOUTS = 'analyzeLayouts' 132 133 _nextUnusedWindowSuffix = 1 134 135 _LF = '\n' 136 _CRLF = '\r\n' 137 _UTF8 = 'UTF8' 138 139 _GET_PARAM_HIGHLIGHT_COMPONENT = "highlightComponent" 140 141
142 - def __init__(self, application):
143 144 self._application = application 145 146 self._currentlyOpenWindowsInClient = dict() 147 148 self._dirtyPaintables = list() 149 150 self._paintableIdMap = dict() 151 152 self._idPaintableMap = dict() 153 154 self._idSequence = 0 155 156 # Note that this is only accessed from synchronized block and 157 # thus should be thread-safe. 158 self._closingWindowName = None 159 160 self._locales = None 161 162 self._pendingLocalesIndex = None 163 164 self._timeoutInterval = -1 165 166 self._dragAndDropService = None 167 168 self._requestThemeName = None 169 170 self._maxInactiveInterval = None 171 172 self.requireLocale(str(application.getLocale())) 173 174 self._typeToKey = dict() 175 self._nextTypeKey = 0 176 177 self._highLightedPaintable = None
178 179
180 - def getApplication(self):
181 return self._application
182 183 184 @classmethod
185 - def _readLine(cls, stream):
186 return stream.readline()
187 188
189 - def doHandleSimpleMultipartFileUpload(self, request, response, 190 streamVariable, variableName, owner, boundary):
191 """Method used to stream content from a multipart request (either 192 from servlet or portlet request) to given StreamVariable. 193 194 @raise IOException: 195 """ 196 # multipart parsing, supports only one file for request, but that is 197 # fine for our current terminal 198 199 inputStream = request.getInputStream() 200 201 contentLength = request.getContentLength() 202 203 atStart = False 204 firstFileFieldFound = False 205 206 rawfilename = 'unknown' 207 rawMimeType = 'application/octet-stream' 208 209 # Read the stream until the actual file starts (empty line). Read 210 # filename and content type from multipart headers. 211 while not atStart: 212 readLine = inputStream.readline() 213 contentLength -= len(readLine) + 2 214 if (readLine.startswith('Content-Disposition:') 215 and readLine.find('filename=') > 0): 216 rawfilename = readLine.replace('.*filename=', '') 217 parenthesis = rawfilename[:1] 218 rawfilename = rawfilename[1:] 219 rawfilename = rawfilename[:rawfilename.find(parenthesis)] 220 firstFileFieldFound = True 221 elif firstFileFieldFound and readLine == '': 222 atStart = True 223 elif readLine.startswith('Content-Type'): 224 rawMimeType = readLine.split(': ')[1] 225 226 contentLength -= (len(boundary) + len(self._CRLF) 227 + (2 * len(self._DASHDASH)) + 2) # 2 == CRLF 228 229 # Reads bytes from the underlying stream. Compares the read bytes to 230 # the boundary string and returns -1 if met. 231 # 232 # The matching happens so that if the read byte equals to the first 233 # char of boundary string, the stream goes to "buffering mode". In 234 # buffering mode bytes are read until the character does not match 235 # the corresponding from boundary string or the full boundary string 236 # is found. 237 # 238 # Note, if this is someday needed elsewhere, don't shoot yourself to 239 # foot and split to a top level helper class. 240 simpleMultiPartReader = \ 241 SimpleMultiPartInputStream(inputStream, boundary, self) 242 243 # Should report only the filename even if the browser sends the path 244 filename = self.removePath(rawfilename) 245 mimeType = rawMimeType 246 247 try: 248 # safe cast as in GWT terminal all variable owners are expected 249 # to be components. 250 component = owner 251 if component.isReadOnly(): 252 raise UploadException('Warning: file upload ignored ' 253 'because the component was read-only') 254 255 forgetVariable = self.streamToReceiver(simpleMultiPartReader, 256 streamVariable, filename, mimeType, contentLength) 257 if forgetVariable: 258 self.cleanStreamVariable(owner, variableName) 259 except Exception, e: 260 self.handleChangeVariablesError(self._application, 261 owner, e, dict()) 262 263 self.sendUploadResponse(request, response)
264 265
266 - def doHandleXhrFilePost(self, request, response, streamVariable, 267 variableName, owner, contentLength):
268 """Used to stream plain file post (aka XHR2.post(File)) 269 270 @raise IOException: 271 """ 272 # These are unknown in filexhr ATM, maybe add to Accept header that 273 # is accessible in portlets 274 filename = 'unknown' 275 mimeType = filename 276 stream = request.getInputStream() 277 278 try: 279 # safe cast as in GWT terminal all variable owners are expected 280 # to be components. 281 component = owner 282 if component.isReadOnly(): 283 raise UploadException('Warning: file upload ignored' 284 ' because the component was read-only') 285 286 forgetVariable = self.streamToReceiver(stream, streamVariable, 287 filename, mimeType, contentLength) 288 if forgetVariable: 289 self.cleanStreamVariable(owner, variableName) 290 except Exception, e: 291 self.handleChangeVariablesError(self._application, owner, e, 292 dict()) 293 294 self.sendUploadResponse(request, response)
295 296
297 - def streamToReceiver(self, inputStream, streamVariable, filename, typ, 298 contentLength):
299 """@return: true if the streamvariable has informed that the terminal 300 can forget this variable 301 @raise UploadException: 302 """ 303 if streamVariable is None: 304 raise ValueError, 'StreamVariable for the post not found' 305 306 out = None 307 totalBytes = 0 308 startedEvent = StreamingStartEventImpl(filename, typ, contentLength) 309 310 try: 311 streamVariable.streamingStarted(startedEvent) 312 out = streamVariable.getOutputStream() 313 listenProgress = streamVariable.listenProgress() 314 315 # Gets the output target stream 316 if out is None: 317 raise NoOutputStreamException() 318 319 if inputStream is None: 320 # No file, for instance non-existent filename in html upload 321 raise NoInputStreamException() 322 323 bufferSize = self._MAX_UPLOAD_BUFFER_SIZE 324 bytesReadToBuffer = 0 325 while totalBytes < len(inputStream): 326 buff = inputStream.read(bufferSize) 327 bytesReadToBuffer = inputStream.pos - bytesReadToBuffer 328 329 out.write(buff) 330 totalBytes += bytesReadToBuffer 331 332 if listenProgress: 333 # update progress if listener set and contentLength 334 # received 335 progressEvent = StreamingProgressEventImpl(filename, 336 typ, contentLength, totalBytes) 337 streamVariable.onProgress(progressEvent) 338 339 if streamVariable.isInterrupted(): 340 raise UploadInterruptedException() 341 342 # upload successful 343 out.close() 344 event = StreamingEndEventImpl(filename, typ, totalBytes) 345 streamVariable.streamingFinished(event) 346 except UploadInterruptedException, e: 347 # Download interrupted by application code 348 self.tryToCloseStream(out) 349 event = StreamingErrorEventImpl(filename, typ, contentLength, 350 totalBytes, e) 351 streamVariable.streamingFailed(event) 352 # Note, we are not throwing interrupted exception forward as 353 # it is not a terminal level error like all other exception. 354 except Exception, e: 355 self.tryToCloseStream(out) 356 event = StreamingErrorEventImpl(filename, typ, contentLength, 357 totalBytes, e) 358 streamVariable.streamingFailed(event) 359 # throw exception for terminal to be handled (to be passed 360 # to terminalErrorHandler) 361 raise UploadException(e) 362 363 return startedEvent.isDisposed()
364 365
366 - def tryToCloseStream(self, out):
367 try: 368 # try to close output stream (e.g. file handle) 369 if out is not None: 370 out.close() 371 except IOError: 372 pass # NOP
373 374 375 @classmethod
376 - def removePath(cls, filename):
377 """Removes any possible path information from the filename and 378 returns the filename. Separators / and \\ are used. 379 """ 380 if filename is not None: 381 filename = re.sub('^.*[/\\\\]', '', filename) 382 383 return filename
384 385
386 - def sendUploadResponse(self, request, response):
387 """@raise IOException: 388 """ 389 response.setContentType('text/html') 390 out = response.getOutputStream() 391 out.write('<html><body>download handled</body></html>') 392 out.flush() 393 out.close()
394 395
396 - def doHandleUidlRequest(self, request, response, callback, window):
397 """Internally process a UIDL request from the client. 398 399 This method calls L{handleVariables} to process any changes to 400 variables by the client and then repaints affected components 401 using L{paintAfterVariableChanges}. 402 403 Also, some cleanup is done when a request arrives for an application 404 that has already been closed. 405 406 The method handleUidlRequest() in subclasses should call this method. 407 408 @param request: 409 @param response: 410 @param callback: 411 @param window: 412 target window for the UIDL request, can be null if target 413 not found 414 @raise IOException: 415 @raise InvalidUIDLSecurityKeyException: 416 """ 417 self._requestThemeName = request.getParameter('theme') 418 419 self._maxInactiveInterval = \ 420 request.getSession().getMaxInactiveInterval() 421 422 # repaint requested or session has timed out and new one is created 423 repaintAll = request.getParameter( 424 self._GET_PARAM_REPAINT_ALL) is not None 425 # || (request.getSession().isNew()); FIXME: What the h*ll is this?? 426 427 out = response.getOutputStream() 428 429 analyzeLayouts = False 430 if repaintAll: 431 # analyzing can be done only with repaintAll 432 analyzeLayouts = request.getParameter( 433 self._GET_PARAM_ANALYZE_LAYOUTS) is not None 434 435 param = request.getParameter(self._GET_PARAM_HIGHLIGHT_COMPONENT) 436 if param != None: 437 pid = request.getParameter(self._GET_PARAM_HIGHLIGHT_COMPONENT) 438 highLightedPaintable = self._idPaintableMap.get(pid) 439 self.highlightPaintable(highLightedPaintable) 440 441 outWriter = out 442 443 # The rest of the process is synchronized with the application 444 # in order to guarantee that no parallel variable handling is 445 # made 446 if self._application.isRunning(): 447 # Returns if no window found 448 if window is None: 449 # This should not happen, no windows exists but 450 # application is still open. 451 logger.warning('Could not get window for application ' 452 'with request ID ' + request.getRequestID()) 453 return 454 else: 455 # application has been closed 456 self.endApplication(request, response, self._application) 457 return 458 459 # Change all variables based on request parameters 460 if not self.handleVariables(request, response, callback, 461 self._application, window): 462 463 # var inconsistency; the client is probably out-of-sync 464 ci = None 465 try: 466 ci = self._application.__class__.getSystemMessages() 467 except Exception: 468 # FIXME: Handle exception 469 # Not critical, but something is still wrong; 470 # print stacktrace 471 logger.warning('getSystemMessages() failed - continuing') 472 473 if ci is not None: 474 msg = ci.getOutOfSyncMessage() 475 cap = ci.getOutOfSyncCaption() 476 if (msg is not None) or (cap is not None): 477 callback.criticalNotification(request, response, cap, 478 msg, None, ci.getOutOfSyncURL()) 479 # will reload page after this 480 return 481 482 # No message to show, let's just repaint all. 483 repaintAll = True 484 485 self.paintAfterVariableChanges(request, response, callback, repaintAll, 486 outWriter, window, analyzeLayouts) 487 488 if self._closingWindowName is not None: 489 if self._closingWindowName in self._currentlyOpenWindowsInClient: 490 del self._currentlyOpenWindowsInClient[self._closingWindowName] 491 492 self._closingWindowName = None 493 494 # Finds the window within the application 495 #outWriter.close() 496 self._requestThemeName = None
497 498
499 - def highlightPaintable(self, highLightedPaintable2):
500 sb = StringIO() 501 sb.write("*** Debug details of a component: *** \n") 502 sb.write("Type: ") 503 sb.write(clsname(highLightedPaintable2)) 504 if isinstance(highLightedPaintable2, AbstractComponent): 505 component = highLightedPaintable2 506 sb.write("\nId:") 507 idd = self._paintableIdMap.get(component) 508 sb.write(idd if idd is not None else 'null') 509 if component.getCaption() is not None: 510 sb.write("\nCaption:") 511 sb.write(component.getCaption()) 512 513 self.printHighlightedComponentHierarchy(sb, component) 514 515 logger.info(sb.getvalue()) 516 sb.close()
517 518
519 - def printHighlightedComponentHierarchy(self, sb, component):
520 h = list() 521 h.append(component) 522 parent = component.getParent() 523 while parent is not None: 524 h.insert(0, parent) 525 parent = parent.getParent() 526 527 sb.write("\nComponent hierarchy:\n") 528 application2 = component.getApplication() 529 sb.write(clsname(application2)) 530 sb.write(".") 531 sb.write(application2.__class__.__name__) 532 sb.write("(") 533 sb.write(application2.__class__.__name__); 534 sb.write(".java") 535 sb.write(":1)") 536 l = 1 537 for component2 in h: 538 sb.write("\n") 539 for _ in range(l): 540 sb.write(" ") 541 l += 1 542 componentClass = component2.__class__ 543 topClass = componentClass 544 #while topClass.getEnclosingClass() != None: 545 # topClass = topClass.getEnclosingClass() 546 547 sb.write(clsname(componentClass)) 548 sb.write(".") 549 sb.write(componentClass.__name__); 550 sb.write("(") 551 sb.write(topClass.__name__) 552 sb.write(".java:1)")
553 554
555 - def paintAfterVariableChanges(self, request, response, callback, 556 repaintAll, outWriter, window, analyzeLayouts):
557 """@raise PaintException: 558 @raise IOException: 559 """ 560 if repaintAll: 561 self.makeAllPaintablesDirty(window) 562 563 # Removes application if it has stopped during variable changes 564 if not self._application.isRunning(): 565 self.endApplication(request, response, self._application) 566 return 567 568 self.openJsonMessage(outWriter, response) 569 570 # security key 571 writeSecurityTokenFlag = \ 572 request.getAttribute(self._WRITE_SECURITY_TOKEN_FLAG, None) 573 574 if writeSecurityTokenFlag is not None: 575 seckey = request.getSession().getAttribute( 576 ApplicationConnection.UIDL_SECURITY_TOKEN_ID, None) 577 if seckey is None: 578 seckey = str( uuid.uuid4() ) 579 request.getSession().setAttribute( 580 ApplicationConnection.UIDL_SECURITY_TOKEN_ID, seckey) 581 582 outWriter.write('\"' 583 + ApplicationConnection.UIDL_SECURITY_TOKEN_ID + '\":\"') 584 outWriter.write(seckey) 585 outWriter.write('\",') 586 587 # If the browser-window has been closed - we do not need to paint 588 # it at all 589 if window.getName() == self._closingWindowName: 590 outWriter.write('\"changes\":[]') 591 else: 592 # re-get window - may have been changed 593 newWindow = self.doGetApplicationWindow(request, callback, 594 self._application, window) 595 if newWindow != window: 596 window = newWindow 597 repaintAll = True 598 599 self.writeUidlResponce(callback, repaintAll, outWriter, window, 600 analyzeLayouts) 601 602 self.closeJsonMessage(outWriter)
603 604 #outWriter.close() 605 606
607 - def writeUidlResponce(self, callback, repaintAll, outWriter, window, 608 analyzeLayouts):
609 610 outWriter.write('\"changes\":[') 611 612 paintables = None 613 614 invalidComponentRelativeSizes = None 615 616 paintTarget = JsonPaintTarget(self, outWriter, not repaintAll) 617 windowCache = self._currentlyOpenWindowsInClient.get(window.getName()) 618 if windowCache is None: 619 windowCache = OpenWindowCache() 620 self._currentlyOpenWindowsInClient[window.getName()] = windowCache 621 622 # Paints components 623 if repaintAll: 624 paintables = list() 625 paintables.append(window) 626 627 # Reset sent locales 628 self._locales = None 629 self.requireLocale( self._application.getLocale() ) 630 else: 631 # remove detached components from paintableIdMap so they 632 # can be GC'ed 633 # TODO figure out if we could move this beyond the painting phase, 634 # "respond as fast as possible, then do the cleanup". Beware of 635 # painting the dirty detatched components. 636 for p in self._paintableIdMap.keys(): 637 if p.getApplication() is None: 638 self.unregisterPaintable(p) 639 if self._paintableIdMap[p] in self._idPaintableMap: 640 del self._idPaintableMap[self._paintableIdMap[p]] 641 if p in self._paintableIdMap: 642 del self._paintableIdMap[p] 643 if p in self._dirtyPaintables: 644 self._dirtyPaintables.remove(p) 645 646 paintables = self.getDirtyVisibleComponents(window) 647 648 if paintables is not None: 649 # We need to avoid painting children before parent. 650 # This is ensured by ordering list by depth in component 651 # tree 652 653 def compare(c1, c2): 654 d1 = 0 655 while c1.getParent() is not None: 656 d1 += 1 657 c1 = c1.getParent() 658 d2 = 0 659 while c2.getParent() is not None: 660 d2 += 1 661 c2 = c2.getParent() 662 if d1 < d2: 663 return -1 664 if d1 > d2: 665 return 1 666 return 0
667 668 paintables.sort(cmp=compare) 669 670 for p in paintables: 671 # TODO CLEAN 672 if isinstance(p, Window): 673 w = p 674 if w.getTerminal() is None: 675 w.setTerminal( 676 self._application.getMainWindow().getTerminal()) 677 678 # This does not seem to happen in tk5, but remember this case: 679 # else if (p instanceof IComponent) { if (((IComponent) 680 # p).getParent() == null || ((IComponent) p).getApplication() == 681 # null) { // IComponent requested repaint, but is no // longer 682 # attached: skip paintablePainted(p); continue; } } 683 684 # TODO we may still get changes that have been 685 # rendered already (changes with only cached flag) 686 if paintTarget.needsToBePainted(p): 687 paintTarget.startTag('change') 688 paintTarget.addAttribute('format', 'uidl') 689 pid = self.getPaintableId(p) 690 paintTarget.addAttribute('pid', pid) 691 692 p.paint(paintTarget) 693 694 paintTarget.endTag('change') 695 696 self.paintablePainted(p) 697 698 if analyzeLayouts: 699 700 # FIXME: circular import 701 from muntjac.terminal.gwt.server.component_size_validator \ 702 import ComponentSizeValidator 703 704 w = p 705 invalidComponentRelativeSizes = ComponentSizeValidator.\ 706 validateComponentRelativeSizes(w.getContent(), 707 None, None) 708 709 # Also check any existing subwindows 710 if w.getChildWindows() is not None: 711 for subWindow in w.getChildWindows(): 712 invalidComponentRelativeSizes = \ 713 ComponentSizeValidator.\ 714 validateComponentRelativeSizes( 715 subWindow.getContent(), 716 invalidComponentRelativeSizes, 717 None) 718 719 paintTarget.close() 720 outWriter.write(']') # close changes 721 722 outWriter.write(', \"meta\" : {') 723 metaOpen = False 724 725 if repaintAll: 726 metaOpen = True 727 outWriter.write('\"repaintAll\":true') 728 if analyzeLayouts: 729 outWriter.write(', \"invalidLayouts\":') 730 outWriter.write('[') 731 if invalidComponentRelativeSizes is not None: 732 first = True 733 for invalidLayout in invalidComponentRelativeSizes: 734 if not first: 735 outWriter.write(',') 736 else: 737 first = False 738 invalidLayout.reportErrors(outWriter, self, stderr) 739 outWriter.write(']') 740 741 if self._highLightedPaintable is not None: 742 outWriter.write(", \"hl\":\"") 743 idd = self._paintableIdMap.get(self._highLightedPaintable) 744 outWriter.write(idd if idd is not None else 'null') 745 outWriter.write("\"") 746 self._highLightedPaintable = None 747 748 ci = None 749 try: 750 ci = self._application.getSystemMessages() 751 except AttributeError, e: 752 logger.warning('getSystemMessages() failed - continuing') 753 754 # meta instruction for client to enable auto-forward to 755 # sessionExpiredURL after timer expires. 756 if ((ci is not None) and (ci.getSessionExpiredMessage() is None) 757 and (ci.getSessionExpiredCaption() is None) 758 and ci.isSessionExpiredNotificationEnabled()): 759 newTimeoutInterval = self.getTimeoutInterval() 760 if repaintAll or (self._timeoutInterval != newTimeoutInterval): 761 if ci.getSessionExpiredURL() is None: 762 escapedURL = '' 763 else: 764 escapedURL = ci.getSessionExpiredURL().replace('/', '\\/') 765 if metaOpen: 766 outWriter.write(',') 767 768 outWriter.write('\"timedRedirect\":{\"interval\":' 769 + newTimeoutInterval + 15 + ',\"url\":\"' 770 + escapedURL + '\"}') 771 metaOpen = True 772 773 self._timeoutInterval = newTimeoutInterval 774 775 outWriter.write('}, \"resources\" : {') 776 777 # Precache custom layouts 778 779 # TODO We should only precache the layouts that are not 780 # cached already (plagiate from usedPaintableTypes) 781 resourceIndex = 0 782 for resource in paintTarget.getUsedResources(): 783 is_ = None 784 try: 785 is_ = callback.getThemeResourceAsStream(self.getTheme(window), 786 resource) 787 except IOError, e: 788 # FIXME: Handle exception 789 logger.info('Failed to get theme resource stream.') 790 791 if is_ is not None: 792 outWriter.write((', ' if resourceIndex > 0 else '') 793 + '\"' + resource + '\" : ') 794 resourceIndex += 1 # post increment 795 layout = str() 796 try: 797 layout = is_.read() 798 except IOError, e: 799 # FIXME: Handle exception 800 logger.info('Resource transfer failed: ' + str(e)) 801 802 outWriter.write('\"%s\"' % JsonPaintTarget.escapeJSON(layout)) 803 else: 804 # FIXME: Handle exception 805 logger.critical('CustomLayout not found: ' + resource) 806 807 outWriter.write('}') 808 809 usedPaintableTypes = paintTarget.getUsedPaintableTypes() 810 typeMappingsOpen = False 811 for class1 in usedPaintableTypes: 812 if windowCache.cache(class1): 813 # client does not know the mapping key for this type, 814 # send mapping to client 815 if not typeMappingsOpen: 816 typeMappingsOpen = True 817 outWriter.write(', \"typeMappings\" : { ') 818 else: 819 outWriter.write(' , ') 820 canonicalName = clsname(class1) 821 822 if canonicalName.startswith('muntjac.ui'): 823 # use Muntjac package names FIXME: Python client side 824 canonicalName = 'com.vaadin.ui.' + class1.__name__ 825 elif canonicalName.startswith('muntjac.demo.sampler'): 826 canonicalName = 'com.vaadin.demo.sampler.' + class1.__name__ 827 elif hasattr(class1, 'TYPE_MAPPING'): 828 canonicalName = getattr(class1, 'TYPE_MAPPING') 829 else: 830 raise ValueError('type mapping name [%s]' % canonicalName) 831 832 outWriter.write('\"') 833 outWriter.write(canonicalName) 834 outWriter.write('\" : ') 835 outWriter.write(self.getTagForType(class1)) 836 837 if typeMappingsOpen: 838 outWriter.write(' }') 839 840 # add any pending locale definitions requested by the client 841 self.printLocaleDeclarations(outWriter) 842 843 if self._dragAndDropService is not None: 844 self._dragAndDropService.printJSONResponse(outWriter)
845 846
847 - def getTimeoutInterval(self):
848 return self._maxInactiveInterval
849 850
851 - def getTheme(self, window):
852 themeName = window.getTheme() 853 requestThemeName = self.getRequestTheme() 854 855 if requestThemeName is not None: 856 themeName = requestThemeName 857 858 if themeName is None: 859 themeName = AbstractApplicationServlet.getDefaultTheme() 860 861 return themeName
862 863
864 - def getRequestTheme(self):
865 return self._requestThemeName
866 867
868 - def makeAllPaintablesDirty(self, window):
869 # If repaint is requested, clean all ids in this root window 870 for key in self._idPaintableMap.keys(): 871 c = self._idPaintableMap[key] 872 873 if self.isChildOf(window, c): 874 if key in self._idPaintableMap: 875 del self._idPaintableMap[key] 876 877 if c in self._paintableIdMap: 878 del self._paintableIdMap[c] 879 880 # clean WindowCache 881 openWindowCache = \ 882 self._currentlyOpenWindowsInClient.get(window.getName()) 883 884 if openWindowCache is not None: 885 openWindowCache.clear()
886 887
888 - def unregisterPaintable(self, p):
889 """Called when communication manager stops listening for repaints 890 for given component. 891 """ 892 p.removeListener(self, IRepaintRequestListener)
893 894
895 - def handleVariables(self, request, response, callback, application2, 896 window):
897 """If this method returns false, something was submitted that we did 898 not expect; this is probably due to the client being out-of-sync 899 and sending variable changes for non-existing pids 900 901 @return: true if successful, false if there was an inconsistency 902 """ 903 success = True 904 905 changes = self.getRequestPayload(request) 906 if changes is not None: 907 908 # Manage bursts one by one 909 bursts = re.split(self.VAR_BURST_SEPARATOR, changes) 910 # FIXME: remove trailing empty string using re 911 if (len(bursts) > 0) & (bursts[-1] == ''): 912 bursts = bursts[:-1] 913 914 # Security: double cookie submission pattern unless disabled by 915 # property 916 if application2.getProperty(AbstractApplicationServlet.\ 917 SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION) != 'true': 918 if len(bursts) == 1 and 'init' == bursts[0]: 919 # init request; don't handle any variables, key sent in 920 # response. 921 request.setAttribute( 922 self._WRITE_SECURITY_TOKEN_FLAG, True) 923 return True 924 else: 925 # ApplicationServlet has stored the security token in the 926 # session; check that it matched the one sent in the UIDL 927 sessId = request.getSession().getAttribute( 928 ApplicationConnection.UIDL_SECURITY_TOKEN_ID, '') 929 930 if (sessId is None) or (sessId != bursts[0]): 931 msg = 'Security key mismatch' 932 raise InvalidUIDLSecurityKeyException(msg) 933 934 for bi in range(1, len(bursts)): 935 burst = bursts[bi] 936 success = self.handleVariableBurst(request, application2, 937 success, burst) 938 939 # In case that there were multiple bursts, we know that this 940 # is a special synchronous case for closing window. Thus we 941 # are not interested in sending any UIDL changes back to 942 # client. Still we must clear component tree between bursts 943 # to ensure that no removed components are updated. The 944 # painting after the last burst is handled normally by the 945 # calling method. 946 if bi < (len(bursts) - 1): 947 # We will be discarding all changes 948 outWriter = cStringIO() 949 self.paintAfterVariableChanges(request, response, 950 callback, True, outWriter, window, False) 951 952 # Note that we ignore inconsistencies while handling unload request. 953 # The client can't remove invalid variable changes from the burst, and 954 # we don't have the required logic implemented on the server side. E.g. 955 # a component is removed in a previous burst. 956 return success or (self._closingWindowName is not None)
957 958
959 - def handleVariableBurst(self, source, app, success, burst):
960 # extract variables to two dim string array 961 tmp = re.split(self._VAR_RECORD_SEPARATOR, burst) 962 variableRecords = [None] * len(tmp) 963 964 for i in range(len(tmp)): 965 variableRecords[i] = re.split(self._VAR_FIELD_SEPARATOR, tmp[i]) 966 967 i = 0 968 while i < len(variableRecords): 969 variable = variableRecords[i] 970 nextVariable = None 971 if (i + 1) < len(variableRecords): 972 nextVariable = variableRecords[i + 1] 973 974 owner = self.getVariableOwner( variable[self._VAR_PID] ) 975 if (owner is not None) and owner.isEnabled(): 976 m = dict() 977 if ((nextVariable is not None) and (variable[self._VAR_PID] 978 == nextVariable[self._VAR_PID])): 979 # we have more than one value changes in row for 980 # one variable owner, collect em in HashMap 981 m[variable[self._VAR_NAME]] = \ 982 self.convertVariableValue(variable[self._VAR_TYPE][0], 983 variable[self._VAR_VALUE]) 984 else: 985 # use optimized single value map 986 m[variable[self._VAR_NAME]] = \ 987 self.convertVariableValue(variable[self._VAR_TYPE][0], 988 variable[self._VAR_VALUE]) 989 990 # collect following variable changes for this owner 991 while (nextVariable is not None and variable[self._VAR_PID] \ 992 == nextVariable[self._VAR_PID]): 993 i += 1 994 variable = nextVariable 995 if (i + 1) < len(variableRecords): 996 nextVariable = variableRecords[i + 1] 997 else: 998 nextVariable = None 999 1000 m[variable[self._VAR_NAME]] = \ 1001 self.convertVariableValue(variable[self._VAR_TYPE][0], 1002 variable[self._VAR_VALUE]) 1003 1004 try: 1005 self.changeVariables(source, owner, m) 1006 1007 # Special-case of closing browser-level windows: 1008 # track browser-windows currently open in client 1009 if isinstance(owner, Window) and owner.getParent() is None: 1010 close = m.get('close') 1011 if (close is not None) and bool(close): 1012 self._closingWindowName = owner.getName() 1013 1014 except Exception, e: 1015 if isinstance(owner, IComponent): 1016 self.handleChangeVariablesError(app, owner, e, m) 1017 else: 1018 # TODO DragDropService error handling 1019 raise RuntimeError(e) 1020 else: 1021 # Handle special case where window-close is called 1022 # after the window has been removed from the 1023 # application or the application has closed 1024 if (variable[self._VAR_NAME] == 'close' 1025 and variable[self._VAR_VALUE] == 'true'): 1026 # Silently ignore this 1027 i += 1 1028 continue 1029 1030 # Ignore variable change 1031 msg = 'Warning: Ignoring variable change for ' 1032 if owner is not None: 1033 msg += 'disabled component ' + str(owner.__class__) 1034 caption = owner.getCaption() 1035 if caption is not None: 1036 msg += ', caption=' + caption 1037 else: 1038 msg += ('non-existent component, VAR_PID=' 1039 + variable[self._VAR_PID]) 1040 success = False 1041 1042 logger.warning(msg) 1043 1044 i += 1 1045 1046 return success
1047 1048
1049 - def changeVariables(self, source, owner, m):
1050 owner.changeVariables(source, m)
1051 1052
1053 - def getVariableOwner(self, string):
1054 owner = self._idPaintableMap.get(string) 1055 if (owner is None) and string.startswith('DD'): 1056 return self.getDragAndDropService() 1057 return owner
1058 1059
1060 - def getDragAndDropService(self):
1061 if self._dragAndDropService is None: 1062 self._dragAndDropService = DragAndDropService(self) 1063 return self._dragAndDropService
1064 1065
1066 - def getRequestPayload(self, request):
1067 """Reads the request data from the Request and returns it converted 1068 to an UTF-8 string. 1069 1070 @raise IOException: 1071 """ 1072 requestLength = request.getContentLength() 1073 if requestLength == 0: 1074 return None 1075 1076 inputStream = request.getInputStream() 1077 if inputStream is not None: 1078 return inputStream.read() 1079 else: 1080 return None
1081 1082
1083 - def handleChangeVariablesError(self, application, owner, e, m):
1084 """Handles an error (exception) that occurred when processing variable 1085 changes from the client or a failure of a file upload. 1086 1087 For L{AbstractField} components, C{AbstractField.handleError()} 1088 is called. In all other cases (or if the field does not handle the 1089 error), L{ErrorListener.terminalError} for the application error 1090 handler is called. 1091 1092 @param application: 1093 @param owner: 1094 component that the error concerns 1095 @param e: 1096 exception that occurred 1097 @param m: 1098 map from variable names to values 1099 """ 1100 handled = False 1101 errorEvent = ChangeVariablesErrorEvent(owner, e, m) 1102 1103 if isinstance(owner, AbstractField): 1104 try: 1105 handled = owner.handleError(errorEvent) 1106 except Exception, handlerException: 1107 # If there is an error in the component error handler we pass 1108 # the that error to the application error handler and continue 1109 # processing the actual error 1110 application.getErrorHandler().terminalError( 1111 ErrorHandlerErrorEvent(handlerException)) 1112 handled = False 1113 1114 if not handled: 1115 application.getErrorHandler().terminalError(errorEvent)
1116 1117
1118 - def convertVariableValue(self, variableType, strValue):
1119 m = { 1120 self._VTYPE_ARRAY: lambda s: self.convertArray(s), 1121 self._VTYPE_MAP: lambda s: self.convertMap(s), 1122 self._VTYPE_STRINGARRAY: lambda s: self.convertStringArray(s), 1123 self._VTYPE_STRING: lambda s: self.decodeVariableValue(s), 1124 self._VTYPE_INTEGER: lambda s: int(s), 1125 self._VTYPE_LONG: lambda s: long(s), 1126 self._VTYPE_FLOAT: lambda s: float(s), 1127 self._VTYPE_DOUBLE: lambda s: float(s), 1128 self._VTYPE_BOOLEAN: lambda s: s.lower() == 'true', 1129 self._VTYPE_PAINTABLE: lambda s: self._idPaintableMap.get(s) 1130 }.get(variableType) 1131 1132 if m is not None: 1133 return m(strValue) 1134 else: 1135 return None
1136 1137
1138 - def convertMap(self, strValue):
1139 parts = strValue.split(self.VAR_ARRAYITEM_SEPARATOR) 1140 mapp = dict() 1141 i = 0 1142 while i < len(parts): 1143 key = parts[i] 1144 if len(key) > 0: 1145 variabletype = key[0] 1146 # decode encoded separators 1147 decodedValue = self.decodeVariableValue(parts[i + 1]) 1148 decodedKey = self.decodeVariableValue(key[1:]) 1149 value = self.convertVariableValue(variabletype, decodedValue) 1150 mapp[decodedKey] = value 1151 i += 2 1152 return mapp
1153 1154
1155 - def convertStringArray(self, strValue):
1156 # need to return delimiters and filter them out; otherwise empty 1157 # strings are lost 1158 # an extra empty delimiter at the end is automatically eliminated 1159 arrayItemSeparator = self.VAR_ARRAYITEM_SEPARATOR 1160 splitter = re.compile('(\\' + arrayItemSeparator + '+)') 1161 1162 tokens = list() 1163 prevToken = arrayItemSeparator 1164 for token in splitter.split(strValue): 1165 if arrayItemSeparator != token: 1166 # decode encoded separators 1167 tokens.append(self.decodeVariableValue(token)) 1168 elif arrayItemSeparator == prevToken: 1169 tokens.append('') 1170 prevToken = token 1171 1172 return tokens
1173 1174
1175 - def convertArray(self, strValue):
1176 val = strValue.split(self.VAR_ARRAYITEM_SEPARATOR) 1177 1178 if len(val) == 0 or (len(val) == 1 and len(val[0]) == 0): 1179 return [] 1180 1181 values = [None] * len(val) 1182 for i in range(len(values)): 1183 string = val[i] 1184 # first char of string is type 1185 variableType = string[0] 1186 values[i] = self.convertVariableValue(variableType, string[1:]) 1187 1188 return values
1189 1190
1191 - def decodeVariableValue(self, encodedValue):
1192 """Decode encoded burst, record, field and array item separator 1193 characters in a variable value String received from the client. 1194 This protects from separator injection attacks. 1195 1196 @param encodedValue: value to decode 1197 @return: decoded value 1198 """ 1199 iterator = iter(encodedValue) 1200 1201 try: 1202 character = iterator.next() 1203 except StopIteration: 1204 return '' 1205 1206 result = StringIO() 1207 while True: 1208 try: 1209 if self.VAR_ESCAPE_CHARACTER == character: 1210 character = iterator.next() 1211 if character == chr(ord(self.VAR_ESCAPE_CHARACTER) + 0x30): 1212 # escaped escape character 1213 result.write(self.VAR_ESCAPE_CHARACTER) 1214 1215 elif character == chr(ord(self.VAR_BURST_SEPARATOR) + 0x30): 1216 pass 1217 elif character == chr(ord(self._VAR_RECORD_SEPARATOR)+0x30): 1218 pass 1219 elif character == chr(ord(self._VAR_FIELD_SEPARATOR) +0x30): 1220 pass 1221 elif (character == 1222 chr(ord(self.VAR_ARRAYITEM_SEPARATOR) + 0x30)): 1223 # +0x30 makes these letters for easier reading 1224 result.write( chr(ord(character) - 0x30) ) 1225 else: 1226 # other escaped character - probably a client-server 1227 # version mismatch 1228 raise ValueError("Invalid escaped character from the " 1229 "client - check that the widgetset and server " 1230 "versions match") 1231 else: 1232 # not a special character - add it to the result as is 1233 result.write(character) 1234 1235 character = iterator.next() 1236 1237 except StopIteration: 1238 break 1239 1240 r = result.getvalue() 1241 result.close() 1242 return r
1243 1244
1245 - def printLocaleDeclarations(self, outWriter):
1246 """Prints the queued (pending) locale definitions to a PrintWriter 1247 in a (UIDL) format that can be sent to the client and used there in 1248 formatting dates, times etc. 1249 """ 1250 # Send locale informations to client 1251 outWriter.write(', \"locales\":[') 1252 1253 while self._pendingLocalesIndex < len(self._locales): 1254 l = self.generateLocale(self._locales[self._pendingLocalesIndex]) 1255 1256 # Locale name 1257 outWriter.write('{\"name\":\"' + str(l) + '\",') 1258 1259 # Month names (both short and full) 1260 months = l.months['format']['wide'].values() 1261 short_months = l.months['format']['abbreviated'].values() 1262 1263 outWriter.write(('\"smn\":[\"' 1264 + short_months[0] + '\",\"' + short_months[1] + '\",\"' 1265 + short_months[2] + '\",\"' + short_months[3] + '\",\"' 1266 + short_months[4] + '\",\"' + short_months[5] + '\",\"' 1267 + short_months[6] + '\",\"' + short_months[7] + '\",\"' 1268 + short_months[8] + '\",\"' + short_months[9] + '\",\"' 1269 + short_months[10] + '\",\"' + short_months[11] + '\"' 1270 + '],').encode('utf-8')) 1271 outWriter.write(('\"mn\":[\"' 1272 + months[0] + '\",\"' + months[1] + '\",\"' 1273 + months[2] + '\",\"' + months[3] + '\",\"' 1274 + months[4] + '\",\"' + months[5] + '\",\"' 1275 + months[6] + '\",\"' + months[7] + '\",\"' 1276 + months[8] + '\",\"' + months[9] + '\",\"' 1277 + months[10] + '\",\"' + months[11] + '\"' 1278 + '],').encode('utf-8')) 1279 1280 # Weekday names (both short and full) 1281 days = l.days['format']['wide'].values() 1282 short_days = l.days['format']['abbreviated'].values() 1283 outWriter.write(('\"sdn\":[\"' 1284 + short_days[6] + '\",\"' 1285 + short_days[0] + '\",\"' + short_days[1] + '\",\"' 1286 + short_days[2] + '\",\"' + short_days[3] + '\",\"' 1287 + short_days[4] + '\",\"' + short_days[5] + '\"' 1288 + '],').encode('utf-8')) 1289 outWriter.write(('\"dn\":[\"' 1290 + days[6] + '\",\"' 1291 + days[0] + '\",\"' + days[1] + '\",\"' 1292 + days[2] + '\",\"' + days[3] + '\",\"' 1293 + days[4] + '\",\"' + days[5] + '\"' 1294 + '],').encode('utf-8')) 1295 1296 # First day of week 1297 # (Babel: 6 = sunday, 0 = monday, Vaadin: 0 = sunday, 1 = monday) 1298 fdow = l.first_week_day 1299 if fdow == 0: 1300 fdow = 1 1301 else: 1302 fdow = 0 1303 outWriter.write('\"fdow\":' + str(fdow) + ',') 1304 1305 # Date formatting (MM/DD/YYYY etc.) 1306 try: 1307 df = l.date_formats['short'].pattern 1308 df += ' ' 1309 df += l.time_formats['short'].pattern 1310 df = df.encode('utf-8') # convert unicode to string 1311 except KeyError: 1312 logger.warning('Unable to get default date ' 1313 'pattern for locale ' + str(l)) 1314 #df = locale.nl_langinfo(locale.D_T_FMT) 1315 df = 'dd/MM/yy HH:mm' 1316 1317 timeStart = df.find('H') 1318 if timeStart < 0: 1319 timeStart = df.find('h') 1320 ampm_first = df.find('a') 1321 # E.g. in Korean locale AM/PM is before h:mm 1322 # TODO should take that into consideration on client-side as well, 1323 # now always h:mm a 1324 if ampm_first > 0 and ampm_first < timeStart: 1325 timeStart = ampm_first 1326 # Hebrew locale has time before the date 1327 timeFirst = timeStart == 0 1328 if timeFirst: 1329 dateStart = df.find(' ') 1330 if ampm_first > dateStart: 1331 dateStart = df.find(' ', ampm_first) 1332 dateformat = df[dateStart + 1:] 1333 else: 1334 dateformat = df[:timeStart - 1] 1335 1336 outWriter.write('\"df\":\"' + dateformat.strip() + '\",') 1337 1338 # Time formatting (24 or 12 hour clock and AM/PM suffixes) 1339 timeformat = df[timeStart:len(df)] 1340 1341 # Doesn't return second or milliseconds. 1342 # 1343 # We use timeformat to determine 12/24-hour clock 1344 twelve_hour_clock = timeformat.find('a') > -1 1345 1346 # TODO there are other possibilities as well, like 'h' in french 1347 # (ignore them, too complicated) 1348 hour_min_delimiter = '.' if timeformat.find('.') > -1 else ':' 1349 1350 # outWriter.write("\"tf\":\"" + timeformat + "\","); 1351 outWriter.write('\"thc\":' + str(twelve_hour_clock).lower() + ',') 1352 outWriter.write('\"hmd\":\"' + hour_min_delimiter + '\"') 1353 if twelve_hour_clock: 1354 ampm = [( l.periods['am'] ).encode('utf-8'), 1355 ( l.periods['pm'] ).encode('utf-8')] 1356 outWriter.write(',\"ampm\":[\"' + ampm[0] + '\",\"' 1357 + ampm[1] + '\"]') 1358 outWriter.write('}') 1359 if self._pendingLocalesIndex < len(self._locales) - 1: 1360 outWriter.write(',') 1361 1362 self._pendingLocalesIndex += 1 1363 1364 outWriter.write(']') # close locales
1365 1366
1367 - def doGetApplicationWindow(self, request, callback, application, 1368 assumedWindow):
1369 1370 window = None 1371 1372 # If the client knows which window to use, use it if possible 1373 windowClientRequestedName = request.getParameter('windowName') 1374 1375 if ((assumedWindow is not None) 1376 and (assumedWindow in application.getWindows())): 1377 windowClientRequestedName = assumedWindow.getName() 1378 1379 if windowClientRequestedName is not None: 1380 window = application.getWindow(windowClientRequestedName) 1381 if window is not None: 1382 return window 1383 1384 # If client does not know what window it wants 1385 if (window is None) and not request.isRunningInPortlet(): 1386 # This is only supported if the application is running inside a 1387 # servlet 1388 1389 # Get the path from URL 1390 path = callback.getRequestPathInfo(request) 1391 1392 # If the path is specified, create name from it. 1393 # 1394 # An exception is if UIDL request have come this far. This happens 1395 # if main window is refreshed. In that case we always return main 1396 # window (infamous hacky support for refreshes if only main window 1397 # is used). However we are not returning with main window here (we 1398 # will later if things work right), because the code is so cryptic 1399 # that nobody really knows what it does. 1400 pathMayContainWindowName = (path is not None 1401 and len(path) > 0 and not (path == '/')) 1402 1403 if pathMayContainWindowName: 1404 uidlRequest = path.startswith('/UIDL') 1405 if not uidlRequest: 1406 windowUrlName = None 1407 if path[0] == '/': 1408 path = path[1:] 1409 index = path.find('/') 1410 if index < 0: 1411 windowUrlName = path 1412 path = '' 1413 else: 1414 windowUrlName = path[:index] 1415 path = path[index + 1:] 1416 window = application.getWindow(windowUrlName) 1417 1418 # By default, use mainwindow 1419 if window is None: 1420 window = application.getMainWindow() 1421 # Return null if no main window was found 1422 if window is None: 1423 return None 1424 1425 # If the requested window is already open, resolve conflict 1426 if window.getName() in self._currentlyOpenWindowsInClient: 1427 newWindowName = window.getName() 1428 1429 while newWindowName in self._currentlyOpenWindowsInClient: 1430 newWindowName = (window.getName() + '_' + 1431 str(self._nextUnusedWindowSuffix)) 1432 self._nextUnusedWindowSuffix += 1 1433 1434 window = application.getWindow(newWindowName) 1435 1436 # If everything else fails, use main window even in case of 1437 # conflicts 1438 if window is None: 1439 window = application.getMainWindow() 1440 1441 return window
1442 1443
1444 - def endApplication(self, request, response, application):
1445 """Ends the Application. 1446 1447 The browser is redirected to the Application logout URL set with 1448 L{Application.setLogoutURL}, or to the application URL if no logout 1449 URL is given. 1450 1451 @param request: 1452 the request instance. 1453 @param response: 1454 the response to write to. 1455 @param application: 1456 the Application to end. 1457 @raise IOException: 1458 if the writing failed due to input/output error. 1459 """ 1460 logoutUrl = application.getLogoutURL() 1461 if logoutUrl is None: 1462 logoutUrl = application.getURL() 1463 1464 # clients JS app is still running, send a special json file to tell 1465 # client that application has quit and where to point browser now 1466 # Set the response type 1467 outWriter = response.getOutputStream() 1468 self.openJsonMessage(outWriter, response) 1469 outWriter.write('\"redirect\":{') 1470 outWriter.write('\"url\":\"' + logoutUrl + '\"}') 1471 self.closeJsonMessage(outWriter) 1472 outWriter.flush()
1473 1474
1475 - def closeJsonMessage(self, outWriter):
1476 outWriter.write('}]')
1477 1478
1479 - def openJsonMessage(self, outWriter, response):
1480 """Writes the opening of JSON message to be sent to client. 1481 """ 1482 # Sets the response type 1483 response.setContentType('application/json; charset=UTF-8') 1484 # some dirt to prevent cross site scripting 1485 outWriter.write('for(;;);[{')
1486 1487
1488 - def getPaintableId(self, paintable):
1489 """Gets the IPaintable Id. If IPaintable has debug id set it will be 1490 used prefixed with "PID_S". Otherwise a sequenced ID is created. 1491 1492 @param paintable: 1493 @return: the paintable Id. 1494 """ 1495 idd = self._paintableIdMap.get(paintable) 1496 if idd is None: 1497 # use testing identifier as id if set 1498 idd = paintable.getDebugId() 1499 if idd is None: 1500 idd = 'PID' + str(self._idSequence) 1501 self._idSequence += 1 # post increment 1502 else: 1503 idd = 'PID_S' + idd 1504 1505 old = self._idPaintableMap[idd] = paintable 1506 if (old is not None) and (old != paintable): 1507 # Two paintables have the same id. We still make sure the 1508 # old one is a component which is still attached to the 1509 # application. This is just a precaution and should not be 1510 # absolutely necessary. 1511 1512 if (isinstance(old, IComponent) 1513 and (old.getApplication() is not None)): 1514 raise ValueError(('Two paintables (' 1515 + paintable.__class__.__name__ 1516 + ',' + old.__class__.__name__ 1517 + ') have been assigned the same id: ' 1518 + paintable.getDebugId())) 1519 1520 self._paintableIdMap[paintable] = idd 1521 1522 return idd
1523 1524
1525 - def hasPaintableId(self, paintable):
1526 return paintable in self._paintableIdMap
1527 1528
1529 - def getDirtyVisibleComponents(self, w):
1530 """Returns dirty components which are in given window. Components 1531 in an invisible subtrees are omitted. 1532 1533 @param w: 1534 root window for which dirty components is to be fetched 1535 """ 1536 resultset = list(self._dirtyPaintables) 1537 1538 # The following algorithm removes any components that would be painted 1539 # as a direct descendant of other components from the dirty components 1540 # list. The result is that each component should be painted exactly 1541 # once and any unmodified components will be painted as "cached=true". 1542 1543 for p in self._dirtyPaintables: 1544 if isinstance(p, IComponent): 1545 component = p 1546 if component.getApplication() is None: 1547 # component is detached after requestRepaint is called 1548 resultset.remove(p) 1549 self._dirtyPaintables.remove(p) 1550 else: 1551 componentsRoot = component.getWindow() 1552 if componentsRoot is None: 1553 # This should not happen unless somebody has overriden 1554 # getApplication or getWindow in an illegal way. 1555 raise ValueError('component.getWindow() returned null ' 1556 'for a component attached to the application') 1557 1558 if componentsRoot.getParent() is not None: 1559 # this is a subwindow 1560 componentsRoot = componentsRoot.getParent() 1561 1562 if componentsRoot != w: 1563 resultset.remove(p) 1564 elif ((component.getParent() is not None) 1565 and not component.getParent().isVisible()): 1566 # Do not return components in an invisible subtree. 1567 # 1568 # Components that are invisible in visible subree, must 1569 # be rendered (to let client know that they need to be 1570 # hidden). 1571 resultset.remove(p) 1572 1573 return resultset
1574 1575
1576 - def repaintRequested(self, event):
1577 """@see: L{IRepaintRequestListener.repaintRequested}""" 1578 p = event.getPaintable() 1579 if p not in self._dirtyPaintables: 1580 self._dirtyPaintables.append(p)
1581 1582
1583 - def paintablePainted(self, paintable):
1584 """Internally mark a L{IPaintable} as painted and start 1585 collecting new repaint requests for it. 1586 """ 1587 if paintable in self._dirtyPaintables: 1588 self._dirtyPaintables.remove(paintable) 1589 paintable.requestRepaintRequests()
1590 1591
1592 - def requireLocale(self, value):
1593 """Queues a locale to be sent to the client (browser) for date and 1594 time entry etc. All locale specific information is derived from 1595 server-side L{Locale} instances and sent to the client when 1596 needed, eliminating the need to use the L{Locale} class and all 1597 the framework behind it on the client. 1598 """ 1599 if self._locales is None: 1600 self._locales = list() 1601 l = self._application.getLocale() 1602 self._locales.append(str(l)) 1603 self._pendingLocalesIndex = 0 1604 1605 if str(value) not in self._locales: 1606 self._locales.append(str(value))
1607 1608
1609 - def generateLocale(self, value):
1610 """Constructs a L{Locale} instance to be sent to the client based on 1611 a short locale description string. 1612 1613 @see: L{requireLocale} 1614 """ 1615 temp = value.split('_') 1616 if len(temp) == 1: 1617 return Locale(temp[0], '') 1618 elif len(temp) == 2: 1619 return Locale(temp[0], temp[1]) 1620 else: 1621 return Locale(temp[0], temp[1])#, temp[2]) 1622 return value
1623 1624 1625 @classmethod
1626 - def isChildOf(cls, parent, child):
1627 """Helper method to test if a component contains another. 1628 """ 1629 p = child.getParent() 1630 while p is not None: 1631 if parent == p: 1632 return True 1633 p = p.getParent() 1634 return False
1635 1636
1637 - def handleURI(self, window, request, response, callback):
1638 """Calls the Window URI handler for a request and returns the 1639 L{DownloadStream} returned by the handler. 1640 1641 If the window is the main window of an application, the (deprecated) 1642 L{Application.handleURI} is called first 1643 to handle L{ApplicationResource}s, and the window handler is 1644 only called if it returns null. 1645 1646 @param window: 1647 the target window of the request 1648 @param request: 1649 the request instance 1650 @param response: 1651 the response to write to 1652 @return: DownloadStream if the request was handled and further 1653 processing should be suppressed, null otherwise. 1654 @see: L{URIHandler} 1655 """ 1656 warn("deprecated", DeprecationWarning) 1657 1658 uri = callback.getRequestPathInfo(request) 1659 1660 # If no URI is available 1661 if uri is None: 1662 uri = '' 1663 else: 1664 # Removes the leading / 1665 while uri.startswith('/') and len(uri) > 0: 1666 uri = uri[1:] 1667 1668 # Handles the uri 1669 try: 1670 context = self._application.getURL() 1671 if window == self._application.getMainWindow(): 1672 stream = None 1673 # Application.handleURI run first. Handles possible 1674 # ApplicationResources. 1675 stream = self._application.handleURI(context, uri) 1676 if stream is None: 1677 stream = window.handleURI(context, uri) 1678 return stream 1679 else: 1680 # Resolve the prefix end index 1681 index = uri.find('/') 1682 if index > 0: 1683 prefix = uri[:index] 1684 windowContext = urljoin(context, prefix + '/') 1685 if len(uri) > len(prefix) + 1: 1686 windowUri = uri[len(prefix) + 1:] 1687 else: 1688 windowUri = '' 1689 return window.handleURI(windowContext, windowUri) 1690 else: 1691 return None 1692 except Exception, t: 1693 event = URIHandlerErrorImpl(self._application, t) 1694 self._application.getErrorHandler().terminalError(event) 1695 return None
1696 1697
1698 - def getTagForType(self, class1):
1699 obj = self._typeToKey.get(class1) 1700 if obj is None: 1701 obj = self._nextTypeKey 1702 self._nextTypeKey += 1 1703 self._typeToKey[class1] = obj 1704 return str(obj)
1705 1706
1707 - def getStreamVariableTargetUrl(self, owner, name, value):
1708 raise NotImplementedError
1709 1710
1711 - def cleanStreamVariable(self, owner, name):
1712 raise NotImplementedError
1713
1714 1715 -class IRequest(object):
1716 """Generic interface of a (HTTP or Portlet) request to the application. 1717 1718 This is a wrapper interface that allows 1719 L{AbstractCommunicationManager} to use a unified API. 1720 1721 @author: peholmst 1722 """ 1723
1724 - def getSession(self):
1725 """Gets a L{Session} wrapper implementation representing the 1726 session for which this request was sent. 1727 1728 Multiple Muntjac applications can be associated with a single session. 1729 1730 @return: Session 1731 """ 1732 raise NotImplementedError
1733 1734
1735 - def isRunningInPortlet(self):
1736 """Are the applications in this session running in a portlet or 1737 directly as servlets. 1738 1739 @return: true if in a portlet 1740 """ 1741 raise NotImplementedError
1742 1743
1744 - def getParameter(self, name):
1745 """Get the named HTTP or portlet request parameter. 1746 """ 1747 raise NotImplementedError
1748 1749
1750 - def getContentLength(self):
1751 """Returns the length of the request content that can be read from the 1752 input stream returned by L{getInputStream}. 1753 1754 @return: content length in bytes 1755 """ 1756 raise NotImplementedError
1757 1758
1759 - def getInputStream(self):
1760 """Returns an input stream from which the request content can be read. 1761 The request content length can be obtained with 1762 L{getContentLength} without reading the full stream contents. 1763 1764 @raise IOException: 1765 """ 1766 raise NotImplementedError
1767 1768
1769 - def getRequestID(self):
1770 """Returns the request identifier that identifies the target Muntjac 1771 window for the request. 1772 1773 @return: String identifier for the request target window 1774 """ 1775 raise NotImplementedError
1776 1777
1778 - def getAttribute(self, name):
1779 raise NotImplementedError
1780 1781
1782 - def setAttribute(self, name, value):
1783 raise NotImplementedError
1784 1785
1786 - def getWrappedRequest(self):
1787 """Gets the underlying request object. The request is typically either 1788 a L{ServletRequest} or a L{PortletRequest}. 1789 1790 @return: wrapped request object 1791 """ 1792 raise NotImplementedError
1793
1794 1795 -class IResponse(object):
1796 """Generic interface of a (HTTP or Portlet) response from the application. 1797 1798 This is a wrapper interface that allows L{AbstractCommunicationManager} to 1799 use a unified API. 1800 1801 @author: peholmst 1802 """ 1803
1804 - def getOutputStream(self):
1805 """Gets the output stream to which the response can be written. 1806 1807 @raise IOException: 1808 """ 1809 raise NotImplementedError
1810 1811
1812 - def setContentType(self, typ):
1813 """Sets the MIME content type for the response to be communicated 1814 to the browser. 1815 """ 1816 raise NotImplementedError
1817 1818
1819 - def getWrappedResponse(self):
1820 """Gets the wrapped response object, usually a class implementing 1821 either L{ServletResponse}. 1822 1823 @return: wrapped request object 1824 """ 1825 raise NotImplementedError
1826
1827 1828 -class ISession(object):
1829 """Generic wrapper interface for a (HTTP or Portlet) session. 1830 1831 Several applications can be associated with a single session. 1832 1833 @author: peholmst 1834 """ 1835
1836 - def isNew(self):
1837 raise NotImplementedError
1838 1839
1840 - def getAttribute(self, name):
1841 raise NotImplementedError
1842 1843
1844 - def setAttribute(self, name, o):
1845 raise NotImplementedError
1846 1847
1848 - def getMaxInactiveInterval(self):
1849 raise NotImplementedError
1850 1851
1852 - def getWrappedSession(self):
1853 raise NotImplementedError
1854
1855 1856 -class ICallback(object):
1857 """@author: peholmst 1858 """ 1859
1860 - def criticalNotification(self, request, response, cap, msg, details, 1861 outOfSyncURL):
1862 raise NotImplementedError
1863 1864
1865 - def getRequestPathInfo(self, request):
1866 raise NotImplementedError
1867 1868
1869 - def getThemeResourceAsStream(self, themeName, resource):
1870 raise NotImplementedError
1871
1872 1873 -class UploadInterruptedException(Exception):
1874
1875 - def __init__(self):
1876 msg = 'Upload interrupted by other thread' 1877 super(UploadInterruptedException, self).__init__(msg)
1878
1879 1880 -class ErrorHandlerErrorEvent(TerminalErrorEvent):
1881
1882 - def __init__(self, throwable):
1883 self._throwable = throwable
1884 1885
1886 - def getThrowable(self):
1887 return self._throwable
1888
1889 1890 -class URIHandlerErrorImpl(URIHandlerErrorEvent):
1891 """Implementation of L{IErrorEvent} interface.""" 1892
1893 - def __init__(self, owner, throwable):
1894 self._owner = owner 1895 self._throwable = throwable
1896 1897
1898 - def getThrowable(self):
1899 """@see: L{IErrorEvent.getThrowable}""" 1900 return self._throwable
1901 1902
1903 - def getURIHandler(self):
1904 """@see: L{IErrorEvent.getURIHandler}""" 1905 return self._owner
1906
1907 1908 -class InvalidUIDLSecurityKeyException(Exception):
1909
1910 - def __init__(self, message):
1911 super(InvalidUIDLSecurityKeyException, self).__init__(message)
1912
1913 1914 -class OpenWindowCache(object):
1915 """Helper class for terminal to keep track of data that client is 1916 expected to know. 1917 1918 TODO: make customlayout templates (from theme) to be cached here. 1919 """ 1920
1921 - def __init__(self):
1922 self._res = set()
1923 1924
1925 - def cache(self, obj):
1926 """@return: true if the given class was added to cache 1927 """ 1928 if obj in self._res: 1929 return False 1930 else: 1931 self._res.add(obj) 1932 return True
1933 1934
1935 - def clear(self):
1936 self._res.clear()
1937
1938 1939 -class SimpleMultiPartInputStream(StringIO): # FIXME InputStream
1940 """Stream that extracts content from another stream until the boundary 1941 string is encountered. 1942 1943 Public only for unit tests, should be considered private for all other 1944 purposes. 1945 """ 1946
1947 - def __init__(self, realInputStream, boundaryString, manager):
1948 super(SimpleMultiPartInputStream, self).__init__() 1949 1950 # Counter of how many characters have been matched to boundary string 1951 # from the stream 1952 self._matchedCount = -1 1953 1954 # Used as pointer when returning bytes after partly matched boundary 1955 # string. 1956 self._curBoundaryIndex = 0 1957 1958 # The byte found after a "promising start for boundary" 1959 self._bufferedByte = -1 1960 1961 self._atTheEnd = False 1962 1963 self._boundary = manager.CRLF + manager.DASHDASH + boundaryString 1964 1965 self._realInputStream = realInputStream
1966 1967
1968 - def getvalue(self):
1969 if self._atTheEnd: 1970 1971 # End boundary reached, nothing more to read 1972 return -1 1973 elif self._bufferedByte >= 0: 1974 1975 # Purge partially matched boundary if there was such 1976 return self.getBuffered() 1977 elif self._matchedCount != -1: 1978 1979 # Special case where last "failed" matching ended with first 1980 # character from boundary string 1981 return self.matchForBoundary() 1982 else: 1983 1984 fromActualStream = self._realInputStream.read() 1985 if fromActualStream == -1: 1986 1987 # unexpected end of stream 1988 raise IOError('The multipart stream ended unexpectedly') 1989 if self._boundary[0] == fromActualStream: 1990 1991 # If matches the first character in boundary string, start 1992 # checking if the boundary is fetched. 1993 return self.matchForBoundary() 1994 1995 return fromActualStream
1996 1997
1998 - def matchForBoundary(self):
1999 """Reads the input to expect a boundary string. Expects that the first 2000 character has already been matched. 2001 2002 @return: -1 if the boundary was matched, else returns the first byte 2003 from boundary 2004 @raise IOException: 2005 """ 2006 self._matchedCount = 0 2007 2008 # Going to "buffered mode". Read until full boundary match or a 2009 # different character. 2010 while True: 2011 self._matchedCount += 1 2012 if self._matchedCount == len(self._boundary): 2013 # The whole boundary matched so we have reached the end of 2014 # file 2015 self._atTheEnd = True 2016 return -1 2017 2018 fromActualStream = self._realInputStream.read() 2019 2020 if fromActualStream != self._boundary[self._matchedCount]: 2021 # Did not find full boundary, cache the mismatching byte 2022 # and start returning the partially matched boundary. 2023 self._bufferedByte = fromActualStream 2024 return self.getBuffered()
2025 2026
2027 - def getBuffered(self):
2028 """Returns the partly matched boundary string and the byte following 2029 that. 2030 2031 @raise IOException: 2032 """ 2033 if self._matchedCount == 0: 2034 # The boundary has been returned, return the buffered byte. 2035 b = self._bufferedByte 2036 self._bufferedByte = -1 2037 self._matchedCount = -1 2038 else: 2039 b = self._boundary[self._curBoundaryIndex] 2040 self._curBoundaryIndex += 1 2041 if self._curBoundaryIndex == self._matchedCount: 2042 # The full boundary has been returned, remaining is the 2043 # char that did not match the boundary. 2044 self._curBoundaryIndex = 0 2045 if self._bufferedByte != self._boundary[0]: 2046 # next call for getBuffered will return the 2047 # bufferedByte that came after the partial boundary 2048 # match 2049 self._matchedCount = 0 2050 else: 2051 # Special case where buffered byte again matches the 2052 # boundaryString. This could be the start of the real 2053 # end boundary. 2054 self._matchedCount = 0 2055 self._bufferedByte = -1 2056 2057 if b == -1: 2058 raise IOError('The multipart stream ended unexpectedly') 2059 2060 return b
2061