1
2
3
4
5
6
7
8
9
10
11
12
13
14
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__)
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
96
97 _WRITE_SECURITY_TOKEN_FLAG = 'writeSecurityToken'
98
99
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
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
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
157
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
181 return self._application
182
183
184 @classmethod
186 return stream.readline()
187
188
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
197
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
210
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)
228
229
230
231
232
233
234
235
236
237
238
239
240 simpleMultiPartReader = \
241 SimpleMultiPartInputStream(inputStream, boundary, self)
242
243
244 filename = self.removePath(rawfilename)
245 mimeType = rawMimeType
246
247 try:
248
249
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
273
274 filename = 'unknown'
275 mimeType = filename
276 stream = request.getInputStream()
277
278 try:
279
280
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
316 if out is None:
317 raise NoOutputStreamException()
318
319 if inputStream is None:
320
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
334
335 progressEvent = StreamingProgressEventImpl(filename,
336 typ, contentLength, totalBytes)
337 streamVariable.onProgress(progressEvent)
338
339 if streamVariable.isInterrupted():
340 raise UploadInterruptedException()
341
342
343 out.close()
344 event = StreamingEndEventImpl(filename, typ, totalBytes)
345 streamVariable.streamingFinished(event)
346 except UploadInterruptedException, e:
347
348 self.tryToCloseStream(out)
349 event = StreamingErrorEventImpl(filename, typ, contentLength,
350 totalBytes, e)
351 streamVariable.streamingFailed(event)
352
353
354 except Exception, e:
355 self.tryToCloseStream(out)
356 event = StreamingErrorEventImpl(filename, typ, contentLength,
357 totalBytes, e)
358 streamVariable.streamingFailed(event)
359
360
361 raise UploadException(e)
362
363 return startedEvent.isDisposed()
364
365
367 try:
368
369 if out is not None:
370 out.close()
371 except IOError:
372 pass
373
374
375 @classmethod
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
394
395
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
423 repaintAll = request.getParameter(
424 self._GET_PARAM_REPAINT_ALL) is not None
425
426
427 out = response.getOutputStream()
428
429 analyzeLayouts = False
430 if repaintAll:
431
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
444
445
446 if self._application.isRunning():
447
448 if window is None:
449
450
451 logger.warning('Could not get window for application '
452 'with request ID ' + request.getRequestID())
453 return
454 else:
455
456 self.endApplication(request, response, self._application)
457 return
458
459
460 if not self.handleVariables(request, response, callback,
461 self._application, window):
462
463
464 ci = None
465 try:
466 ci = self._application.__class__.getSystemMessages()
467 except Exception:
468
469
470
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
480 return
481
482
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
495
496 self._requestThemeName = None
497
498
517
518
553
554
557 """@raise PaintException:
558 @raise IOException:
559 """
560 if repaintAll:
561 self.makeAllPaintablesDirty(window)
562
563
564 if not self._application.isRunning():
565 self.endApplication(request, response, self._application)
566 return
567
568 self.openJsonMessage(outWriter, response)
569
570
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
588
589 if window.getName() == self._closingWindowName:
590 outWriter.write('\"changes\":[]')
591 else:
592
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
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
623 if repaintAll:
624 paintables = list()
625 paintables.append(window)
626
627
628 self._locales = None
629 self.requireLocale( self._application.getLocale() )
630 else:
631
632
633
634
635
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
650
651
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
672 if isinstance(p, Window):
673 w = p
674 if w.getTerminal() is None:
675 w.setTerminal(
676 self._application.getMainWindow().getTerminal())
677
678
679
680
681
682
683
684
685
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
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
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(']')
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
755
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
778
779
780
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
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
795 layout = str()
796 try:
797 layout = is_.read()
798 except IOError, e:
799
800 logger.info('Resource transfer failed: ' + str(e))
801
802 outWriter.write('\"%s\"' % JsonPaintTarget.escapeJSON(layout))
803 else:
804
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
814
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
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
841 self.printLocaleDeclarations(outWriter)
842
843 if self._dragAndDropService is not None:
844 self._dragAndDropService.printJSONResponse(outWriter)
845
846
848 return self._maxInactiveInterval
849
850
862
863
865 return self._requestThemeName
866
867
869
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
881 openWindowCache = \
882 self._currentlyOpenWindowsInClient.get(window.getName())
883
884 if openWindowCache is not None:
885 openWindowCache.clear()
886
887
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
909 bursts = re.split(self.VAR_BURST_SEPARATOR, changes)
910
911 if (len(bursts) > 0) & (bursts[-1] == ''):
912 bursts = bursts[:-1]
913
914
915
916 if application2.getProperty(AbstractApplicationServlet.\
917 SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION) != 'true':
918 if len(bursts) == 1 and 'init' == bursts[0]:
919
920
921 request.setAttribute(
922 self._WRITE_SECURITY_TOKEN_FLAG, True)
923 return True
924 else:
925
926
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
940
941
942
943
944
945
946 if bi < (len(bursts) - 1):
947
948 outWriter = cStringIO()
949 self.paintAfterVariableChanges(request, response,
950 callback, True, outWriter, window, False)
951
952
953
954
955
956 return success or (self._closingWindowName is not None)
957
958
960
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
980
981 m[variable[self._VAR_NAME]] = \
982 self.convertVariableValue(variable[self._VAR_TYPE][0],
983 variable[self._VAR_VALUE])
984 else:
985
986 m[variable[self._VAR_NAME]] = \
987 self.convertVariableValue(variable[self._VAR_TYPE][0],
988 variable[self._VAR_VALUE])
989
990
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
1008
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
1019 raise RuntimeError(e)
1020 else:
1021
1022
1023
1024 if (variable[self._VAR_NAME] == 'close'
1025 and variable[self._VAR_VALUE] == 'true'):
1026
1027 i += 1
1028 continue
1029
1030
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
1051
1052
1054 owner = self._idPaintableMap.get(string)
1055 if (owner is None) and string.startswith('DD'):
1056 return self.getDragAndDropService()
1057 return owner
1058
1059
1061 if self._dragAndDropService is None:
1062 self._dragAndDropService = DragAndDropService(self)
1063 return self._dragAndDropService
1064
1065
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
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
1108
1109
1110 application.getErrorHandler().terminalError(
1111 ErrorHandlerErrorEvent(handlerException))
1112 handled = False
1113
1114 if not handled:
1115 application.getErrorHandler().terminalError(errorEvent)
1116
1117
1136
1137
1153
1154
1156
1157
1158
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
1167 tokens.append(self.decodeVariableValue(token))
1168 elif arrayItemSeparator == prevToken:
1169 tokens.append('')
1170 prevToken = token
1171
1172 return tokens
1173
1174
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
1185 variableType = string[0]
1186 values[i] = self.convertVariableValue(variableType, string[1:])
1187
1188 return values
1189
1190
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
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
1224 result.write( chr(ord(character) - 0x30) )
1225 else:
1226
1227
1228 raise ValueError("Invalid escaped character from the "
1229 "client - check that the widgetset and server "
1230 "versions match")
1231 else:
1232
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
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
1251 outWriter.write(', \"locales\":[')
1252
1253 while self._pendingLocalesIndex < len(self._locales):
1254 l = self.generateLocale(self._locales[self._pendingLocalesIndex])
1255
1256
1257 outWriter.write('{\"name\":\"' + str(l) + '\",')
1258
1259
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
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
1297
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
1306 try:
1307 df = l.date_formats['short'].pattern
1308 df += ' '
1309 df += l.time_formats['short'].pattern
1310 df = df.encode('utf-8')
1311 except KeyError:
1312 logger.warning('Unable to get default date '
1313 'pattern for locale ' + str(l))
1314
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
1322
1323
1324 if ampm_first > 0 and ampm_first < timeStart:
1325 timeStart = ampm_first
1326
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
1339 timeformat = df[timeStart:len(df)]
1340
1341
1342
1343
1344 twelve_hour_clock = timeformat.find('a') > -1
1345
1346
1347
1348 hour_min_delimiter = '.' if timeformat.find('.') > -1 else ':'
1349
1350
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(']')
1365
1366
1442
1443
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
1465
1466
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
1476 outWriter.write('}]')
1477
1478
1480 """Writes the opening of JSON message to be sent to client.
1481 """
1482
1483 response.setContentType('application/json; charset=UTF-8')
1484
1485 outWriter.write('for(;;);[{')
1486
1487
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
1498 idd = paintable.getDebugId()
1499 if idd is None:
1500 idd = 'PID' + str(self._idSequence)
1501 self._idSequence += 1
1502 else:
1503 idd = 'PID_S' + idd
1504
1505 old = self._idPaintableMap[idd] = paintable
1506 if (old is not None) and (old != paintable):
1507
1508
1509
1510
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
1526 return paintable in self._paintableIdMap
1527
1528
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
1539
1540
1541
1542
1543 for p in self._dirtyPaintables:
1544 if isinstance(p, IComponent):
1545 component = p
1546 if component.getApplication() is None:
1547
1548 resultset.remove(p)
1549 self._dirtyPaintables.remove(p)
1550 else:
1551 componentsRoot = component.getWindow()
1552 if componentsRoot is None:
1553
1554
1555 raise ValueError('component.getWindow() returned null '
1556 'for a component attached to the application')
1557
1558 if componentsRoot.getParent() is not None:
1559
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
1567
1568
1569
1570
1571 resultset.remove(p)
1572
1573 return resultset
1574
1575
1577 """@see: L{IRepaintRequestListener.repaintRequested}"""
1578 p = event.getPaintable()
1579 if p not in self._dirtyPaintables:
1580 self._dirtyPaintables.append(p)
1581
1582
1590
1591
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
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])
1622 return value
1623
1624
1625 @classmethod
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
1661 if uri is None:
1662 uri = ''
1663 else:
1664
1665 while uri.startswith('/') and len(uri) > 0:
1666 uri = uri[1:]
1667
1668
1669 try:
1670 context = self._application.getURL()
1671 if window == self._application.getMainWindow():
1672 stream = None
1673
1674
1675 stream = self._application.handleURI(context, uri)
1676 if stream is None:
1677 stream = window.handleURI(context, uri)
1678 return stream
1679 else:
1680
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
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
1708 raise NotImplementedError
1709
1710
1712 raise NotImplementedError
1713
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
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
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
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
1767
1768
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
1779 raise NotImplementedError
1780
1781
1783 raise NotImplementedError
1784
1785
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
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
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
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
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
1837 raise NotImplementedError
1838
1839
1841 raise NotImplementedError
1842
1843
1845 raise NotImplementedError
1846
1847
1849 raise NotImplementedError
1850
1851
1853 raise NotImplementedError
1854
1857 """@author: peholmst
1858 """
1859
1862 raise NotImplementedError
1863
1864
1866 raise NotImplementedError
1867
1868
1870 raise NotImplementedError
1871
1878
1881
1883 self._throwable = throwable
1884
1885
1887 return self._throwable
1888
1891 """Implementation of L{IErrorEvent} interface."""
1892
1894 self._owner = owner
1895 self._throwable = throwable
1896
1897
1899 """@see: L{IErrorEvent.getThrowable}"""
1900 return self._throwable
1901
1902
1904 """@see: L{IErrorEvent.getURIHandler}"""
1905 return self._owner
1906
1912
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
1923
1924
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
1937
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
1966
1967
1996
1997
2025
2026
2061