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

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

   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 servlet that handles all communication between the client and 
  17  the server.""" 
  18   
  19  import re 
  20  import logging 
  21  import mimetypes 
  22   
  23  from time import time 
  24  from warnings import warn 
  25   
  26  from urlparse import urljoin 
  27  from os.path import exists, getmtime 
  28   
  29  try: 
  30      from StringIO import StringIO 
  31  except ImportError, e: 
  32      from StringIO import StringIO 
  33   
  34  from muntjac.util import clsname 
  35  from muntjac.application import Application 
  36   
  37  from muntjac.terminal.gwt.server.constants import Constants 
  38  from muntjac.terminal.gwt.server.json_paint_target import JsonPaintTarget 
  39  from muntjac.terminal.gwt.server.exceptions import ServletException 
  40   
  41  from muntjac.terminal.uri_handler import IErrorEvent as URIHandlerErrorEvent 
  42  from muntjac.terminal.terminal import IErrorEvent as TerminalErrorEvent 
  43   
  44  from muntjac.terminal.gwt.server.paste_wsgi_servlet import PasteWsgiServlet 
  45   
  46  from muntjac.terminal.gwt.server.exceptions import \ 
  47      SessionExpiredException, SystemMessageException 
  48   
  49  from muntjac.terminal.gwt.client.application_connection import \ 
  50      ApplicationConnection 
  51   
  52  from muntjac.terminal.gwt.server.web_application_context import \ 
  53      WebApplicationContext 
  54   
  55  from muntjac.terminal.gwt.server.http_servlet_request_listener import \ 
  56      IHttpServletRequestListener 
  57   
  58  from muntjac.terminal.parameter_handler import \ 
  59      IErrorEvent as ParameterHandlerErrorEvent 
  60   
  61   
  62  logger = logging.getLogger(__name__) 
63 64 65 -class RequestType(object):
66 67 FILE_UPLOAD = 'FILE_UPLOAD' 68 UIDL = 'UIDL' 69 OTHER = 'OTHER' 70 STATIC_FILE = 'STATIC_FILE' 71 APPLICATION_RESOURCE = 'APPLICATION_RESOURCE' 72 73 _values = [FILE_UPLOAD, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE] 74 75 @classmethod
76 - def values(cls):
77 return cls._values[:]
78
79 80 -class AbstractApplicationServlet(PasteWsgiServlet, Constants):
81 """Abstract implementation of the ApplicationServlet which handles all 82 communication between the client and the server. 83 84 It is possible to extend this class to provide own functionality but in 85 most cases this is unnecessary. 86 87 @author: Vaadin Ltd. 88 @author: Richard Lincoln 89 @version: 1.1.2 90 """ 91 92 #: The version number of this release. For example "6.2.0". Always in the 93 # format "major.minor.revision[.build]". The build part is optional. All of 94 # major, minor, revision must be integers. 95 VERSION = None 96 97 #: Major version number. For example 6 in 6.2.0. 98 VERSION_MAJOR = None 99 100 #: Minor version number. For example 2 in 6.2.0. 101 VERSION_MINOR = None 102 103 #: Version revision number. For example 0 in 6.2.0. 104 VERSION_REVISION = None 105 106 #: Build identifier. For example "nightly-20091123-c9963" in 107 # 6.2.0.nightly-20091123-c9963. 108 VERSION_BUILD = None 109 110 #: Initialize version numbers from string replaced by build-script. 111 if '1.1.2' == '@' + 'VERSION' + '@': 112 VERSION = '9.9.9.INTERNAL-DEBUG-BUILD' 113 else: 114 VERSION = '1.1.2' 115 116 digits = VERSION.split('.', 4) 117 VERSION_MAJOR = int(digits[0]) 118 VERSION_MINOR = int(digits[1]) 119 VERSION_REVISION = int(digits[2]) 120 121 if len(digits) == 4: 122 VERSION_BUILD = digits[3] 123 else: 124 VERSION_BUILD = '' 125 126 #: If the attribute is present in the request, a html fragment will be 127 # written instead of a whole page. 128 # 129 # It is set to "true" by the L{ApplicationPortlet} (Portlet 1.0) and 130 # read by L{AbstractApplicationServlet}. 131 REQUEST_FRAGMENT = ''#clsname(ApplicationServlet) + '.fragment' 132 133 #: This request attribute forces widgetsets to be loaded from under the 134 # specified base path; e.g shared widgetset for all portlets in a portal. 135 # 136 # It is set by the L{ApplicationPortlet} (Portlet 1.0) based on 137 # L{Constants.PORTAL_PARAMETER_VAADIN_RESOURCE_PATH} and read by 138 # L{AbstractApplicationServlet}. 139 REQUEST_VAADIN_STATIC_FILE_PATH = ''#clsname(ApplicationServlet) + '.widgetsetPath' 140 141 #: This request attribute forces widgetset used; e.g for portlets that can 142 # not have different widgetsets. 143 # 144 # It is set by the L{ApplicationPortlet} (Portlet 1.0) based on 145 # L{ApplicationPortlet.PORTLET_PARAMETER_WIDGETSET} and read by 146 # L{AbstractApplicationServlet}. 147 REQUEST_WIDGETSET = ''#clsname(ApplicationServlet) + '.widgetset' 148 149 #: This request attribute indicates the shared widgetset (e.g. portal-wide 150 # default widgetset). 151 # 152 # It is set by the L{ApplicationPortlet} (Portlet 1.0) based on 153 # L{Constants.PORTAL_PARAMETER_VAADIN_WIDGETSET} and read by 154 # L{AbstractApplicationServlet}. 155 REQUEST_SHARED_WIDGETSET = ''#clsname(ApplicationServlet) + '.sharedWidgetset' 156 157 #: If set, do not load the default theme but assume that loading it is 158 # handled e.g. by ApplicationPortlet. 159 # 160 # It is set by the L{ApplicationPortlet} (Portlet 1.0) based on 161 # L{Constants.PORTAL_PARAMETER_VAADIN_THEME} and read by 162 # L{AbstractApplicationServlet}. 163 REQUEST_DEFAULT_THEME = ''#clsname(ApplicationServlet) + '.defaultThemeUri' 164 165 #: This request attribute is used to add styles to the main element. E.g 166 # "height:500px" generates a style="height:500px" to the main element, 167 # useful from some embedding situations (e.g portlet include.) 168 # 169 # It is typically set by the L{ApplicationPortlet} (Portlet 1.0) based 170 # on L{ApplicationPortlet.PORTLET_PARAMETER_STYLE} and read by 171 # L{AbstractApplicationServlet}. 172 REQUEST_APPSTYLE = ''#clsname(ApplicationServlet) + '.style' 173 174 UPLOAD_URL_PREFIX = 'APP/UPLOAD/' 175 176
177 - def __init__(self, productionMode=False, debug=False, widgetset=None, 178 resourceCacheTime=3600, disableXsrfProtection=False, 179 *args, **kw_args):
180 181 super(AbstractApplicationServlet, self).__init__(*args, **kw_args) 182 183 self._applicationProperties = dict() 184 self._productionMode = False 185 self._resourcePath = None 186 self._resourceCacheTime = 3600 187 self._firstTransaction = True 188 189 self._applicationProperties[ 190 self.SERVLET_PARAMETER_PRODUCTION_MODE] = \ 191 'true' if productionMode else 'false' 192 193 self._applicationProperties[self.SERVLET_PARAMETER_DEBUG] = \ 194 'true' if debug else 'false' 195 196 self._applicationProperties[self.PARAMETER_WIDGETSET] = \ 197 self.DEFAULT_WIDGETSET if widgetset is None else widgetset 198 199 self._applicationProperties[ 200 self.SERVLET_PARAMETER_RESOURCE_CACHE_TIME] = \ 201 str(resourceCacheTime) 202 203 self._applicationProperties[ 204 self.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION] = \ 205 'true' if disableXsrfProtection else 'false'
206 207
208 - def init(self):
209 """Called by the servlet container to indicate to a servlet that the 210 servlet is being placed into service. 211 """ 212 # Stores the application parameters into Properties object 213 #self._applicationProperties = dict() 214 #for name in CONFIG: 215 # self._applicationProperties[name] = CONFIG[name] 216 217 ## Overrides with server.xml parameters 218 #context = servletConfig.getServletContext() 219 #for name in context.getInitParameterNames(): 220 # self._applicationProperties[name] = context.getInitParameter(name) 221 222 if self._firstTransaction: 223 self._firstTransaction = False 224 self.checkProductionMode() 225 self.checkCrossSiteProtection() 226 self.checkResourceCacheTime()
227 228
229 - def checkCrossSiteProtection(self):
230 if self.getApplicationOrSystemProperty( 231 self.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, 232 'false') == 'true': 233 # Print an information/warning message about running with xsrf 234 # protection disabled 235 logger.warning(self.WARNING_XSRF_PROTECTION_DISABLED)
236 237
238 - def checkWidgetsetVersion(self, request):
239 """Checks that the version reported by the client (widgetset) matches 240 that of the server. 241 """ 242 if not (self.VERSION == self.getParameter(request, 'wsver', '')): 243 # logger.warning(self.WIDGETSET_MISMATCH_INFO % 244 # (self.VERSION, self.getParameter(request, 'wsver', ''))) 245 pass # FIXME: implement Python client side
246 247
248 - def checkProductionMode(self):
249 """Check if the application is in production mode.""" 250 # We are in production mode if debug=false or productionMode=true 251 if self.getApplicationOrSystemProperty( 252 self.SERVLET_PARAMETER_DEBUG, 253 'true') == 'false': 254 # "dDebug=true" is the old way and should no longer be used 255 self._productionMode = True 256 elif self.getApplicationOrSystemProperty( 257 self.SERVLET_PARAMETER_PRODUCTION_MODE, 258 'false') == 'true': 259 # "productionMode=true" is the real way to do it 260 self._productionMode = True 261 262 if not self._productionMode: 263 # Print an information/warning message about running in debug mode 264 logger.warning(self.NOT_PRODUCTION_MODE_INFO)
265 266
267 - def checkResourceCacheTime(self):
268 # Check if the browser caching time has been set in INI 269 # Default is 1h 270 try: 271 rct = self.getApplicationOrSystemProperty( 272 self.SERVLET_PARAMETER_RESOURCE_CACHE_TIME, '3600') 273 self._resourceCacheTime = int(rct) 274 except ValueError: 275 self._resourceCacheTime = 3600 276 logger.warning(self.WARNING_RESOURCE_CACHING_TIME_NOT_NUMERIC)
277 278
279 - def getApplicationProperty(self, parameterName):
280 """Gets an application property value. 281 282 @param parameterName: 283 the Name or the parameter. 284 @return: String value or C{None} if not found 285 """ 286 val = self._applicationProperties.get(parameterName) 287 if val is not None: 288 return val 289 290 # Try lower case application properties for backward compatibility 291 val = self._applicationProperties.get(parameterName.lower()) 292 return val
293 294
295 - def getSystemProperty(self, parameterName):
296 """Gets an system property value. 297 298 @param parameterName: 299 the Name or the parameter. 300 @return: String value or C{None} if not found 301 """ 302 raise NotImplementedError
303 304
305 - def getApplicationOrSystemProperty(self, parameterName, defaultValue):
306 """Gets an application or system property value. 307 308 @param parameterName: 309 the Name or the parameter. 310 @param defaultValue: 311 the Default to be used. 312 @return: String value or default if not found 313 """ 314 # Try application properties 315 val = self.getApplicationProperty(parameterName) 316 if val is not None: 317 return val 318 319 # Try system properties 320 #val = self.getSystemProperty(parameterName) 321 #if val is not None: 322 # return val 323 324 return defaultValue
325 326
327 - def isProductionMode(self):
328 """Returns true if the servlet is running in production mode. 329 Production mode disables all debug facilities. 330 331 @return: true if in production mode, false if in debug mode 332 """ 333 return self._productionMode
334 335
336 - def getResourceCacheTime(self):
337 """Returns the amount of milliseconds the browser should cache a file. 338 Default is 1 hour (3600 ms). 339 340 @return: The amount of milliseconds files are cached in the browser 341 """ 342 return self._resourceCacheTime
343 344
345 - def service(self, request, response):
346 """Receives standard HTTP requests from the public service method and 347 dispatches them. 348 """ 349 requestType = self.getRequestType(request) 350 if not self.ensureCookiesEnabled(requestType, request, response): 351 return 352 353 if requestType == RequestType.STATIC_FILE: 354 self.serveStaticResources(request, response) 355 return 356 357 if self.isRepaintAll(request): 358 # warn if versions do not match 359 self.checkWidgetsetVersion(request) 360 361 application = None 362 transactionStarted = False 363 requestStarted = False 364 365 try: 366 # If a duplicate "close application" URL is received for an 367 # application that is not open, redirect to the application's main 368 # page. 369 # This is needed as e.g. Spring Security remembers the last 370 # URL from the application, which is the logout URL, and repeats 371 # it. 372 # We can tell apart a real onunload request from a repeated one 373 # based on the real one having content (at least the UIDL security 374 # key). 375 if (requestType == RequestType.UIDL 376 and ApplicationConnection.PARAM_UNLOADBURST 377 in self.getParameters(request) 378 and self.getContentLength(request) < 1 379 and self.getExistingApplication(request, False) is None): 380 self.redirectToApplication(request, response) 381 return 382 383 # Find out which application this request is related to 384 application = self.findApplicationInstance(request, requestType) 385 if application is None: 386 return 387 388 # Get or create a WebApplicationContext and an ApplicationManager 389 # for the session 390 webApplicationContext = \ 391 self.getApplicationContext(self.getSession(request)) 392 applicationManager = \ 393 webApplicationContext.getApplicationManager( 394 application, self) 395 396 # Update browser information from the request 397 self.updateBrowserProperties( 398 webApplicationContext.getBrowser(), request) 399 400 # Call application requestStart before Application.init() 401 # (bypasses the limitation in TransactionListener) 402 if isinstance(application, IHttpServletRequestListener): 403 application.onRequestStart(request, response) 404 requestStarted = True 405 406 # Start the newly created application 407 self.startApplication(request, application, webApplicationContext) 408 409 # Transaction starts. Call transaction listeners. Transaction end 410 # is called in the finally block below. 411 webApplicationContext.startTransaction(application, request) 412 transactionStarted = True 413 414 # Handle the request 415 if requestType == RequestType.FILE_UPLOAD: 416 applicationManager.handleFileUpload(request, response, self) 417 return 418 elif requestType == RequestType.UIDL: 419 # Handles AJAX UIDL requests 420 window = applicationManager.getApplicationWindow(request, 421 self, application, None) 422 applicationManager.handleUidlRequest(request, response, 423 self, window) 424 return 425 426 # Removes application if it has stopped (mayby by thread or 427 # transactionlistener) 428 if not application.isRunning(): 429 self.endApplication(request, response, application) 430 return 431 432 # Finds the window within the application 433 window = self.getApplicationWindow(request, applicationManager, 434 application) 435 if window is None: 436 raise ServletException(self.ERROR_NO_WINDOW_FOUND) 437 438 # Sets terminal type for the window, if not already set 439 if window.getTerminal() is None: 440 window.setTerminal(webApplicationContext.getBrowser()) 441 442 # Handle parameters 443 parameters = request.fields() 444 if window is not None and parameters is not None: 445 window.handleParameters(parameters) 446 447 # Call the URI handlers and if this turns out to be a download 448 # request, send the file to the client 449 if self.handleURI(applicationManager, window, request, response): 450 return 451 452 # Send initial AJAX page that kickstarts a Muntjac application 453 self.writeAjaxPage(request, response, window, application) 454 455 except SessionExpiredException, e: 456 # Session has expired, notify user 457 self.handleServiceSessionExpired(request, response) 458 #except GeneralSecurityException, e: 459 # self._handleServiceSecurityException(request, response) 460 # except Exception, e: 461 # self.handleServiceException(request, response, application, e) 462 finally: 463 # Notifies transaction end 464 try: 465 if transactionStarted: 466 application.getContext().endTransaction(application, 467 request) 468 finally: 469 if requestStarted: 470 application.onRequestEnd(request, response)
471 472
473 - def ensureCookiesEnabled(self, requestType, request, response):
474 """Check that cookie support is enabled in the browser. Only checks 475 UIDL requests. 476 477 @param requestType: 478 Type of the request as returned by L{getRequestType} 479 @param request: 480 The request from the browser 481 @param response: 482 The response to which an error can be written 483 @return: false if cookies are disabled, true otherwise 484 @raise IOException: 485 """ 486 if requestType == RequestType.UIDL and not self.isRepaintAll(request): 487 # In all other but the first UIDL request a cookie should be 488 # returned by the browser. 489 # This can be removed if cookieless mode (#3228) is supported 490 if self.getSessionId(request) is None: 491 # User has cookies disabled 492 self.criticalNotification(request, response, 493 self.getSystemMessages().getCookiesDisabledCaption(), 494 self.getSystemMessages().getCookiesDisabledMessage(), 495 None, 496 self.getSystemMessages().getCookiesDisabledURL()) 497 return False 498 499 return True
500 501
502 - def updateBrowserProperties(self, browser, request):
503 # request based details updated always 504 browser.updateRequestDetails(self.getLocale(request), 505 self.getHeader(request, 'REMOTE_ADDR'), 506 self.isSecure(request), 507 self.getUserAgent(request)) 508 509 if request.field('repaintAll', None) is not None: 510 browser.updateClientSideDetails( 511 self.getParameter(request, 'sw', None), 512 self.getParameter(request, 'sh', None), 513 self.getParameter(request, 'tzo', None), 514 self.getParameter(request, 'rtzo', None), 515 self.getParameter(request, 'dstd', None), 516 self.getParameter(request, 'dston', None), 517 self.getParameter(request, 'curdate', None), 518 self.getParameter(request, 'td', None) is not None)
519 520
521 - def criticalNotification(self, request, response, caption, message, 522 details, url):
523 """Send a notification to client's application. Used to notify 524 client of critical errors, session expiration and more. Server 525 has no knowledge of what application client refers to. 526 527 @param request: 528 the HTTP request instance. 529 @param response: 530 the HTTP response to write to. 531 @param caption: 532 the notification caption 533 @param message: 534 to notification body 535 @param details: 536 a detail message to show in addition to the message. 537 Currently shown directly below the message but could be 538 hidden behind a details drop down in the future. Mainly 539 used to give additional information not necessarily 540 useful to the end user. 541 @param url: 542 url to load when the message is dismissed. Null will 543 reload the current page. 544 @raise IOException: 545 if the writing failed due to input/output error. 546 """ 547 if self.isUIDLRequest(request): 548 549 if caption is not None: 550 caption = '\"' + JsonPaintTarget.escapeJSON(caption) + '\"' 551 552 if details is not None: 553 if message is None: 554 message = details 555 else: 556 message += '<br/><br/>' + details 557 558 if message is not None: 559 message = '\"' + JsonPaintTarget.escapeJSON(message) + '\"' 560 else: 561 message = 'null' 562 563 if url is not None: 564 url = '\"' + JsonPaintTarget.escapeJSON(url) + '\"' 565 else: 566 url = 'null' 567 568 output = ('for(;;);[{\"changes\":[], \"meta\" : {' 569 '\"appError\": {' + '\"caption\":' + caption + ',' 570 '\"message\" : ' + message + ',' + '\"url\" : ' + url + 571 '}}, \"resources\": {}, \"locales\":[]}]') 572 573 self.writeResponse( 574 response, 'application/json; charset=UTF-8', output) 575 else: 576 # Create an HTML response with the error 577 output = '' 578 579 if url is not None: 580 output += '<a href=\"' + url + '\">' 581 582 if caption is not None: 583 output += '<b>' + caption + '</b><br/>' 584 585 if message is not None: 586 output += message 587 output += '<br/><br/>' 588 589 if details is not None: 590 output += details 591 output += '<br/><br/>' 592 593 if url is not None: 594 output += '</a>' 595 596 self.writeResponse(response, 'text/html; charset=UTF-8', output)
597 598
599 - def writeResponse(self, response, contentType, output):
600 """Writes the response in C{output} using the contentType given 601 in C{contentType} to the provided L{HttpServletResponse} 602 603 @param response: 604 @param contentType: 605 @param output: 606 Output to write (UTF-8 encoded) 607 @raise IOException: 608 """ 609 self.setHeader(response, 'Content-Type', contentType) 610 self.write(response, output)
611 612
613 - def findApplicationInstance(self, request, requestType):
614 """Returns the application instance to be used for the request. If 615 an existing instance is not found a new one is created or null is 616 returned to indicate that the application is not available. 617 618 @raise ServletException: 619 @raise SessionExpiredException: 620 """ 621 requestCanCreateApplication = \ 622 self.requestCanCreateApplication(request, requestType) 623 624 # Find an existing application for this request. 625 application = self.getExistingApplication(request, 626 requestCanCreateApplication) 627 628 if application is not None: 629 # There is an existing application. We can use this as long as the 630 # user not specifically requested to close or restart it. 631 restartApplication = self.getParameter(request, 632 self.URL_PARAMETER_RESTART_APPLICATION, None) is not None 633 634 closeApplication = self.getParameter(request, 635 self.URL_PARAMETER_CLOSE_APPLICATION, None) is not None 636 637 if restartApplication: 638 session = self.getSession(request, False) 639 self.closeApplication(application, session) 640 return self.createApplication(request) 641 642 elif closeApplication: 643 session = self.getSession(request, False) 644 self.closeApplication(application, session) 645 return None 646 647 else: 648 return application 649 650 # No existing application was found 651 if requestCanCreateApplication: 652 # If the request is such that it should create a new application 653 # if one as not found, we do that. 654 return self.createApplication(request) 655 656 else: 657 # The application was not found and a new one should not be 658 # created. Assume the session has expired. 659 raise SessionExpiredException()
660 661
662 - def requestCanCreateApplication(self, request, requestType):
663 """Check if the request should create an application if an existing 664 application is not found. 665 666 @return: true if an application should be created, false otherwise 667 """ 668 if (requestType == RequestType.UIDL) and self.isRepaintAll(request): 669 # UIDL request contains valid repaintAll=1 event, the user 670 # probably wants to initiate a new application through a custom 671 # index.html without using writeAjaxPage. 672 return True 673 674 elif requestType == RequestType.OTHER: 675 # I.e URIs that are not application resources or static (theme) 676 # files. 677 return True 678 679 return False
680 681
682 - def handleDownload(self, stream, request, response):
683 """Handles the requested URI. An application can add handlers to do 684 special processing, when a certain URI is requested. The handlers 685 are invoked before any windows URIs are processed and if a 686 DownloadStream is returned it is sent to the client. 687 688 @param stream: 689 the download stream. 690 @param request: 691 the HTTP request instance. 692 @param response: 693 the HTTP response to write to. 694 @raise IOException: 695 696 @see: L{URIHandler} 697 """ 698 if stream.getParameter('Location') is not None: 699 self.setStatus(response, 302, 'Found') 700 self.setHeader(response, 'Location', 701 stream.getParameter('Location')) 702 return 703 704 # Download from given stream 705 data = stream.getStream() 706 if data is not None: 707 # Sets content type 708 self.setHeader(response, 'Content-Type', stream.getContentType()) 709 710 # Sets cache headers 711 cacheTime = stream.getCacheTime() 712 if cacheTime <= 0: 713 self.setHeader(response, 'Cache-Control', 'no-cache') 714 self.setHeader(response, 'Pragma', 'no-cache') 715 self.setHeader(response, 'Expires', '0') 716 else: 717 self.setHeader(response, 'Cache-Control', 718 'max-age=' + str(cacheTime / 1000)) 719 self.setHeader(response, 'Expires', 720 str(1000 * time() + cacheTime)) 721 # Required to apply caching in some Tomcats 722 self.setHeader(response, 'Pragma', 'cache') 723 724 # Copy download stream parameters directly 725 # to HTTP headers. 726 names = stream.getParameterNames() 727 if names is not None: 728 for param in names: 729 self.setHeader(response, param, stream.getParameter(param)) 730 731 # suggest local filename from DownloadStream if 732 # Content-Disposition not explicitly set 733 contentDispositionValue = \ 734 stream.getParameter('Content-Disposition') 735 if contentDispositionValue is None: 736 contentDispositionValue = ('filename=\"' 737 + stream.getFileName() + '\"') 738 self.setHeader(response, 'Content-Disposition', 739 contentDispositionValue) 740 741 self.write(response, data.getvalue()) 742 data.close()
743 744
745 - def createApplication(self, request):
746 """Creates a new application and registers it into 747 WebApplicationContext (aka session). This is not meant to be 748 overridden. Override getNewApplication to create the application 749 instance in a custom way. 750 751 @raise ServletException: 752 @raise MalformedURLException: 753 """ 754 newApplication = self.getNewApplication(request) 755 756 context = self.getApplicationContext(self.getSession(request)) 757 context.addApplication(newApplication) 758 759 return newApplication
760 761
762 - def handleServiceException(self, request, response, application, e):
763 # if this was an UIDL request, response UIDL back to client 764 if self.getRequestType(request) == RequestType.UIDL: 765 ci = self.getSystemMessages() 766 767 self.criticalNotification(request, response, 768 ci.getInternalErrorCaption(), 769 ci.getInternalErrorMessage(), 770 None, ci.getInternalErrorURL()) 771 772 if application is not None: 773 application.getErrorHandler().terminalError(RequestError(e)) 774 else: 775 raise ServletException(e) 776 else: 777 # Re-throw other exceptions 778 raise ServletException, str(e)
779 780
781 - def getThemeForWindow(self, request, window):
782 """Returns the theme for given request/window 783 """ 784 # Finds theme name 785 if self.getParameter(request, self.URL_PARAMETER_THEME, 786 None) is not None: 787 themeName = self.getParameter(request, self.URL_PARAMETER_THEME) 788 else: 789 themeName = window.getTheme() 790 791 if themeName is None: 792 # no explicit theme for window defined 793 if self.getParameter(request, self.REQUEST_DEFAULT_THEME, 794 None) is not None: 795 # the default theme is defined in request (by portal) 796 themeName = self.getParameter(request, 797 self.REQUEST_DEFAULT_THEME) 798 else: 799 # using the default theme defined by Muntjac 800 themeName = self.getDefaultTheme() 801 802 # XSS prevention, theme names shouldn't contain special chars anyway. 803 # The servlet denies them via url parameter. 804 themeName = self.stripSpecialChars(themeName) 805 806 return themeName
807 808 809 @classmethod
810 - def stripSpecialChars(cls, themeName):
811 """A helper method to strip away characters that might somehow be 812 used for XSS attacks. Leaves at least alphanumeric characters intact. 813 Also removes eg. ( and ), so values should be safe in javascript too. 814 """ 815 sb = StringIO() 816 for c in themeName: 817 if c not in cls._CHAR_BLACKLIST: 818 sb.write(c) 819 result = sb.getvalue() 820 sb.close() 821 return result
822 823 824 _CHAR_BLACKLIST = ['&', '"', '\'', '<', '>', '(', ')', ';'] 825 826 827 @classmethod
828 - def getDefaultTheme(cls):
829 """Returns the default theme. Must never return C{None}. 830 """ 831 return cls.DEFAULT_THEME_NAME
832 833
834 - def handleURI(self, applicationManager, window, request, response):
835 """Calls URI handlers for the request. If an URI handler returns a 836 DownloadStream the stream is passed to the client for downloading. 837 838 @return: true if an DownloadStream was sent to the client 839 @raise IOException 840 """ 841 # Handles the URI 842 download = applicationManager.handleURI(window, request, response, 843 self) 844 845 # A download request 846 if download is not None: 847 # Client downloads an resource 848 self.handleDownload(download, request, response) 849 return True 850 851 return False
852 853
854 - def handleServiceSessionExpired(self, request, response):
855 if self.isOnUnloadRequest(request): 856 # Request was an unload request (e.g. window close event) and 857 # the client expects no response if it fails. 858 return 859 860 try: 861 ci = self.getSystemMessages() 862 if self.getRequestType(request) != RequestType.UIDL: 863 # 'plain' http req - e.g. browser reload; 864 # just go ahead redirect the browser 865 response.sendRedirect(ci.getSessionExpiredURL()) 866 else: 867 # Invalidate session (weird to have session if we're saying 868 # that it's expired, and worse: portal integration will fail 869 # since the session is not created by the portal. 870 # 871 # Session must be invalidated before criticalNotification 872 # as it commits the response. 873 self.invalidateSession(request) 874 875 # send uidl redirect 876 self.criticalNotification(request, response, 877 ci.getSessionExpiredCaption(), 878 ci.getSessionExpiredMessage(), 879 None, ci.getSessionExpiredURL()) 880 except SystemMessageException, ee: 881 raise ServletException(ee)
882 883
884 - def handleServiceSecurityException(self, request, response):
885 if self.isOnUnloadRequest(request): 886 # Request was an unload request (e.g. window close event) and the 887 # client expects no response if it fails. 888 return 889 890 try: 891 ci = self.getSystemMessages() 892 if self.getRequestType(request) != RequestType.UIDL: 893 # 'plain' http req - e.g. browser reload; 894 # just go ahead redirect the browser 895 self.redirect(response, ci.getCommunicationErrorURL()) 896 else: 897 # send uidl redirect 898 self.criticalNotification(request, response, 899 ci.getCommunicationErrorCaption(), 900 ci.getCommunicationErrorMessage(), 901 self.INVALID_SECURITY_KEY_MSG, 902 ci.getCommunicationErrorURL()) 903 # Invalidate session. Portal integration will fail otherwise 904 # since the session is not created by the portal. 905 self.invalidateSession(request) 906 except SystemMessageException, ee: 907 raise ServletException(ee) 908 909 logger.error('Invalid security key received from ' 910 + request.getRemoteHost())
911 912
913 - def getNewApplication(self, request):
914 """Creates a new application for the given request. 915 916 @param request: 917 the HTTP request. 918 @return: A new Application instance. 919 @raise ServletException: 920 """ 921 raise NotImplementedError
922 923
924 - def startApplication(self, request, application, webApplicationContext):
925 """Starts the application if it is not already running. 926 927 @raise ServletException: 928 @raise MalformedURLException: 929 """ 930 if not application.isRunning(): 931 # Create application 932 applicationUrl = self.getApplicationUrl(request) 933 934 # Initial locale comes from the request 935 lc = self.getLocale(request) 936 application.setLocale(lc) 937 application.start(applicationUrl, self._applicationProperties, 938 webApplicationContext)
939 940
941 - def serveStaticResources(self, request, response):
942 """Check if this is a request for a static resource and, if it is, 943 serve the resource to the client. 944 945 @return: true if a file was served and the request has been handled, 946 false otherwise. 947 @raise IOException: 948 @raise ServletException: 949 """ 950 pathInfo = self.getPathInfo(request) 951 # FIXME: What does 10 refer to? 952 if (pathInfo is None) or (len(pathInfo) <= 10): 953 return False 954 955 contextPath = self.getContextPath(request) 956 if (contextPath is not None 957 and self.getRequestUri(request).startswith('/VAADIN/')): 958 959 self.serveStaticResourcesInVAADIN(self.getRequestUri(request), 960 request, response) 961 return True 962 963 elif self.getRequestUri(request).startswith(contextPath + '/VAADIN/'): 964 965 self.serveStaticResourcesInVAADIN( 966 self.getRequestUri(request)[len(contextPath):], 967 request, response) 968 return True 969 970 return False
971 972
973 - def serveStaticResourcesInVAADIN(self, filename, request, response):
974 """Serve resources from VAADIN directory. 975 976 @param filename: 977 The filename to serve. Should always start with /VAADIN/. 978 @param request: 979 @param response: 980 @raise IOException: 981 @raise ServletException: 982 """ 983 #sc = self.getServletContext() # FIXME: ServletContext 984 resourceUrl = self.getResource(filename) 985 986 if not exists(resourceUrl): 987 # cannot serve requested file 988 msg = 'Requested resource [' + filename + '] not found' 989 logger.info(msg) 990 self.setStatus(response, 404, msg) 991 return 992 993 # security check: do not permit navigation out of the VAADIN 994 # directory 995 if not self.isAllowedVAADINResourceUrl(request, resourceUrl): 996 msg = ('Requested resource [%s] not accessible in the VAADIN ' 997 'directory or access to it is forbidden.' % filename) 998 logger.info(msg) 999 self.setStatus(response, 403, msg) 1000 return 1001 1002 # Find the modification timestamp 1003 lastModifiedTime = 0 1004 try: 1005 lastModifiedTime = int(getmtime(resourceUrl) * 1000) 1006 # Remove milliseconds to avoid comparison problems (milliseconds 1007 # are not returned by the browser in the "If-Modified-Since" 1008 # header). 1009 lastModifiedTime = lastModifiedTime - (lastModifiedTime % 1000) 1010 1011 if self.browserHasNewestVersion(request, lastModifiedTime): 1012 self.setStatus(response, 304, 'Not Modified') 1013 return 1014 except Exception: 1015 # Failed to find out last modified timestamp. Continue without it. 1016 # Set type mime type if we can determine it based on the filename 1017 logger.info('Failed to find out last modified timestamp. ' 1018 + 'Continuing without it.') 1019 1020 mimetype, _ = mimetypes.guess_type(filename) 1021 if mimetype is not None: 1022 self.setHeader(response, 'Content-Type', mimetype) 1023 1024 # Provide modification timestamp to the browser if it is known. 1025 if lastModifiedTime > 0: 1026 self.setHeader(response, 'Last-Modified', str(lastModifiedTime)) 1027 1028 # The browser is allowed to cache for 1 hour without checking if 1029 # the file has changed. This forces browsers to fetch a new version 1030 # when the Muntjac version is updated. This will cause more requests 1031 # to the servlet than without this but for high volume sites the 1032 # static files should never be served through the servlet. The 1033 # cache timeout can be configured by setting the resourceCacheTime 1034 # parameter in web.xml 1035 self.setHeader(response, 'Cache-Control', 'max-age: ' 1036 + str(self._resourceCacheTime)) 1037 1038 # Write the resource to the client. 1039 fd = open(resourceUrl, 'rb') 1040 self.write(response, fd.read()) 1041 fd.close()
1042 1043
1044 - def isAllowedVAADINResourceUrl(self, request, resourceUrl):
1045 """Check whether a URL obtained from a classloader refers to a valid 1046 static resource in the directory VAADIN. 1047 1048 Warning: Overriding of this method is not recommended, but is possible 1049 to support non-default classloaders or servers that may produce URLs 1050 different from the normal ones. The method prototype may change in the 1051 future. Care should be taken not to expose class files or other 1052 resources outside the VAADIN directory if the method is overridden. 1053 """ 1054 # Check that the URL is in a VAADIN directory and does not contain 1055 # "/../" 1056 if ("/VAADIN/" not in self.getUrlPath(resourceUrl) 1057 or "/../" in self.getUrlPath(resourceUrl)): 1058 logger.info("Blocked attempt to access file: " + resourceUrl) 1059 return False 1060 1061 #logger.info("Accepted access to a file using a class loader: " 1062 # + resourceUrl) 1063 return True
1064 1065
1066 - def browserHasNewestVersion(self, request, resourceLastModifiedTimestamp):
1067 """Checks if the browser has an up to date cached version of 1068 requested resource. Currently the check is performed using the 1069 "If-Modified-Since" header. Could be expanded if needed. 1070 1071 @param request: 1072 The HttpServletRequest from the browser. 1073 @param resourceLastModifiedTimestamp: 1074 The timestamp when the resource was last modified. 0 if 1075 the last modification time is unknown. 1076 @return: true if the If-Modified-Since header tells the cached version 1077 in the browser is up to date, false otherwise 1078 """ 1079 if resourceLastModifiedTimestamp < 1: 1080 # We do not know when it was modified so the browser cannot have 1081 # an up-to-date version 1082 return False 1083 1084 # The browser can request the resource conditionally using an 1085 # If-Modified-Since header. Check this against the last modification 1086 # time. 1087 try: 1088 # If-Modified-Since represents the timestamp of the version cached 1089 # in the browser 1090 headerIfModifiedSince = self.getIfModifiedSince(request) 1091 if headerIfModifiedSince >= resourceLastModifiedTimestamp: 1092 # Browser has this an up-to-date version of the resource 1093 return True 1094 except Exception: 1095 # Failed to parse header. Fail silently - the browser does not 1096 # have an up-to-date version in its cache. 1097 pass 1098 1099 return False
1100 1101
1102 - def getRequestType(self, request):
1103 if self.isFileUploadRequest(request): 1104 return RequestType.FILE_UPLOAD 1105 1106 elif self.isUIDLRequest(request): 1107 return RequestType.UIDL 1108 1109 elif self.isStaticResourceRequest(request): 1110 return RequestType.STATIC_FILE 1111 1112 elif self.isApplicationRequest(request): 1113 return RequestType.APPLICATION_RESOURCE 1114 1115 elif self.getParameter(request, 'FileId', None) is not None: # FIXME: getHeader 1116 return RequestType.FILE_UPLOAD 1117 1118 return RequestType.OTHER
1119 1120
1121 - def isApplicationRequest(self, request):
1122 path = self.getRequestPathInfo(request) 1123 1124 if (path is not None) and path.startswith('/APP/'): 1125 return True 1126 1127 return False
1128 1129
1130 - def isStaticResourceRequest(self, request):
1131 pathInfo = self.getPathInfo(request) 1132 1133 if (pathInfo is None) or (len(pathInfo) <= 10): 1134 return False 1135 1136 contextPath = self.getContextPath(request) 1137 if ((contextPath is not None) 1138 and self.getRequestUri(request).startswith('/VAADIN/')): 1139 return True 1140 1141 elif self.getRequestUri(request).startswith(contextPath + '/VAADIN/'): 1142 return True 1143 1144 return False
1145 1146
1147 - def isUIDLRequest(self, request):
1148 pathInfo = self.getRequestPathInfo(request) 1149 1150 if pathInfo is None: 1151 return False 1152 1153 compare = self.AJAX_UIDL_URI 1154 1155 if pathInfo.startswith(compare + '/') or pathInfo.endswith(compare): 1156 return True 1157 1158 return False
1159 1160
1161 - def isFileUploadRequest(self, request):
1162 pathInfo = self.getRequestPathInfo(request) 1163 1164 if pathInfo is None: 1165 return False 1166 1167 if pathInfo.startswith('/' + self.UPLOAD_URL_PREFIX): 1168 return True 1169 1170 return False
1171 1172
1173 - def isOnUnloadRequest(self, request):
1174 param = ApplicationConnection.PARAM_UNLOADBURST 1175 return self.getParameter(request, param, None) is not None
1176 1177
1178 - def getSystemMessages(self):
1179 """Get system messages from the current application class 1180 """ 1181 try: 1182 appCls = self.getApplicationClass() 1183 return appCls.getSystemMessages() 1184 except AttributeError: 1185 raise SystemMessageException( 1186 'Application.getSystemMessage() should be callable') 1187 1188 return Application.getSystemMessages()
1189 1190
1191 - def getApplicationClass(self):
1192 raise NotImplementedError
1193 1194
1195 - def getStaticFilesLocation(self, request):
1196 """Return the URL from where static files, e.g. the widgetset and 1197 the theme, are served. In a standard configuration the VAADIN folder 1198 inside the returned folder is what is used for widgetsets and themes. 1199 1200 The returned folder is usually the same as the context path and 1201 independent of the application. 1202 1203 @return: The location of static resources (should contain the VAADIN 1204 directory). Never ends with a slash (/). 1205 """ 1206 # request may have an attribute explicitly telling location (portal 1207 # case) 1208 param = self.REQUEST_VAADIN_STATIC_FILE_PATH 1209 staticFileLocation = self.getParameter(request, param) 1210 1211 if staticFileLocation is not None: 1212 # TODO remove trailing slash if any? 1213 return staticFileLocation 1214 1215 return self.getWebApplicationsStaticFileLocation(request)
1216 1217
1218 - def getWebApplicationsStaticFileLocation(self, request):
1219 """The default method to fetch static files location (URL). This 1220 method does not check for request attribute 1221 C{REQUEST_VAADIN_STATIC_FILE_PATH}. 1222 """ 1223 # if property is defined in configurations, use that 1224 staticFileLocation = self.getApplicationOrSystemProperty( 1225 self.PARAMETER_VAADIN_RESOURCES, None) 1226 1227 if staticFileLocation is not None: 1228 return staticFileLocation 1229 1230 # the last (but most common) option is to generate default location 1231 # from request 1232 1233 # if context is specified add it to widgetsetUrl 1234 ctxPath = self.getContextPath(request) 1235 1236 # FIXME: ctxPath.length() == 0 condition is probably unnecessary and 1237 # might even be wrong. 1238 if ((len(ctxPath) == 0) 1239 and (self.originalContextPath(request) is not None)): 1240 # include request (e.g portlet), get context path from 1241 # attribute 1242 ctxPath = self.originalContextPath(request) 1243 1244 # Remove heading and trailing slashes from the context path 1245 ctxPath = self.removeHeadingOrTrailing(ctxPath, '/') 1246 1247 if ctxPath == '': 1248 return '' 1249 else: 1250 return '/' + ctxPath
1251 1252 1253 @classmethod
1254 - def removeHeadingOrTrailing(cls, string, what):
1255 """Remove any heading or trailing "what" from the "string". 1256 """ 1257 while string.startswith(what): 1258 string = string[1:] 1259 1260 while string.endswith(what): 1261 string = string[:-1] 1262 1263 return string
1264 1265
1266 - def redirectToApplication(self, request, response):
1267 """Write a redirect response to the main page of the application. 1268 1269 @raise IOException: 1270 if sending the redirect fails due to an input/output 1271 error or a bad application URL 1272 """ 1273 applicationUrl = self.getApplicationUrl(request) 1274 self.sendRedirect(response, applicationUrl) # encodeRedirectURL
1275 1276
1277 - def writeAjaxPage(self, request, response, window, application):
1278 """This method writes the html host page (aka kickstart page) that 1279 starts the actual Muntjac application. 1280 1281 If one needs to override parts of the host page, it is suggested 1282 that one overrides on of several submethods which are called by 1283 this method: 1284 1285 - L{setAjaxPageHeaders} 1286 - L{writeAjaxPageHtmlHeadStart} 1287 - L{writeAjaxPageHtmlHeader} 1288 - L{writeAjaxPageHtmlBodyStart} 1289 - L{writeAjaxPageHtmlMuntjacScripts} 1290 - L{writeAjaxPageHtmlMainDiv} 1291 - L{writeAjaxPageHtmlBodyEnd} 1292 1293 @param request: 1294 the HTTP request. 1295 @param response: 1296 the HTTP response to write to. 1297 @param window: 1298 @param application: 1299 @raise IOException: 1300 if the writing failed due to input/output error. 1301 @raise MalformedURLException: 1302 if the application is denied access the persistent data 1303 store represented by the given URL. 1304 """ 1305 # e.g portlets only want a html fragment 1306 fragment = self.getParameter(request, self.REQUEST_FRAGMENT, 1307 None) is not None 1308 1309 if fragment: 1310 # if this is a fragment request, the actual application is put to 1311 # request so ApplicationPortlet can save it for a later use 1312 self.setParameter(request, clsname(Application), application) 1313 1314 page = StringIO() 1315 1316 if window.getCaption() is None: 1317 title = 'Muntjac 6' 1318 else: 1319 title = window.getCaption() 1320 1321 # Fetch relative url to application 1322 # don't use server and port in uri. It may cause problems with some 1323 # virtual server configurations which lose the server name 1324 appUrl = self.getUrlPath( self.getApplicationUrl(request) ) 1325 if appUrl.endswith('/'): 1326 appUrl = appUrl[:-1] 1327 1328 themeName = self.getThemeForWindow(request, window) 1329 1330 themeUri = self.getThemeUri(themeName, request) 1331 1332 if not fragment: 1333 self.setAjaxPageHeaders(response) 1334 self.writeAjaxPageHtmlHeadStart(page, request) 1335 self.writeAjaxPageHtmlHeader(page, title, themeUri, request) 1336 self.writeAjaxPageHtmlBodyStart(page, request) 1337 1338 appId = appUrl 1339 if '' == appUrl: 1340 appId = 'ROOT' 1341 1342 appId = re.sub('[^a-zA-Z0-9]', '', appId) 1343 1344 # Add hashCode to the end, so that it is still (sort of) predictable, 1345 # but indicates that it should not be used in CSS and such: 1346 hashCode = hash(appId) 1347 if hashCode < 0: 1348 hashCode = -hashCode 1349 1350 appId = appId + '-' + str(hashCode) 1351 1352 self.writeAjaxPageHtmlMuntjacScripts(window, themeName, application, 1353 page, appUrl, themeUri, appId, request) 1354 1355 # - Add classnames; 1356 # .v-app 1357 # .v-app-loading 1358 # .v-app-<simpleName for app class> 1359 # .v-theme-<themeName, remove non-alphanum> 1360 1361 appClass = 'v-app-' + self.getApplicationCSSClassName() 1362 1363 themeClass = '' 1364 if themeName is not None: 1365 themeClass = 'v-theme-' + re.sub('[^a-zA-Z0-9]', '', themeName) 1366 else: 1367 themeClass = ('v-theme-' 1368 + re.sub('[^a-zA-Z0-9]', '', self.getDefaultTheme())) 1369 1370 classNames = 'v-app ' + themeClass + ' ' + appClass 1371 1372 divStyle = None 1373 if self.getParameter(request, self.REQUEST_APPSTYLE, None) is not None: 1374 divStyle = ('style=\"' 1375 + self.getParameter(request, self.REQUEST_APPSTYLE) + '\"') 1376 1377 self.writeAjaxPageHtmlMainDiv(page, appId, classNames, divStyle, 1378 request) 1379 1380 if not fragment: 1381 page.write('</body>\n</html>\n') 1382 1383 self.write(response, page.getvalue()) 1384 page.close()
1385 1386
1387 - def getApplicationCSSClassName(self):
1388 """Returns the application class identifier for use in the 1389 application CSS class name in the root DIV. The application 1390 CSS class name is of form "v-app-"+getApplicationCSSClassName(). 1391 1392 This method should normally not be overridden. 1393 1394 @return: The CSS class name to use in combination with "v-app-". 1395 """ 1396 try: 1397 return self.getApplicationClass().__name__ 1398 except Exception, e: # ClassNotFoundException 1399 logger.warning('getApplicationCSSClassName failed') 1400 return 'unknown'
1401 1402
1403 - def getThemeUri(self, themeName, request):
1404 """Get the URI for the application theme. 1405 1406 A portal-wide default theme is fetched from the portal shared 1407 resource directory (if any), other themes from the portlet. 1408 """ 1409 if themeName == self.getParameter(request, self.REQUEST_DEFAULT_THEME, 1410 None): 1411 # our window theme is the portal wide default theme, make it load 1412 # from portals directory is defined 1413 staticFilePath = self.getStaticFilesLocation(request) 1414 else: 1415 # theme is a custom theme, which is not necessarily located in 1416 # portals VAADIN directory. Let the default servlet conf decide 1417 # (omitting request parameter) the location. Note that theme can 1418 # still be placed to portal directory with servlet parameter. 1419 1420 staticFilePath = self.getWebApplicationsStaticFileLocation(request) 1421 1422 return staticFilePath + '/' + self.THEME_DIRECTORY_PATH + themeName
1423 1424
1425 - def writeAjaxPageHtmlMainDiv(self, page, appId, classNames, divStyle, 1426 request):
1427 """Method to write the div element into which that actual Muntjac 1428 application is rendered. 1429 1430 Override this method if you want to add some custom html around around 1431 the div element into which the actual Muntjac application will be 1432 rendered. 1433 1434 @raise IOException: 1435 """ 1436 page.write('<div id=\"' + appId + '\" class=\"' + classNames + '\" ' \ 1437 + (divStyle if divStyle is not None else '') + '>') 1438 page.write('<div class=\"v-app-loading\"></div>') 1439 page.write('</div>\n') 1440 page.write('<noscript>' + self.getNoScriptMessage() + '</noscript>')
1441 1442
1443 - def writeAjaxPageHtmlMuntjacScripts(self, window, themeName, application, 1444 page, appUrl, themeUri, appId, request):
1445 """Method to write the script part of the page which loads needed 1446 Muntjac scripts and themes. 1447 1448 Override this method if you want to add some custom html around 1449 scripts. 1450 1451 @raise ServletException: 1452 @raise IOException: 1453 """ 1454 # request widgetset takes precedence (e.g portlet include) 1455 requestWidgetset = self.getParameter(request, self.REQUEST_WIDGETSET, 1456 None) 1457 sharedWidgetset = self.getParameter(request, 1458 self.REQUEST_SHARED_WIDGETSET, None) 1459 1460 if requestWidgetset is None and sharedWidgetset is None: 1461 # Use the value from configuration or DEFAULT_WIDGETSET. 1462 # If no shared widgetset is specified, the default widgetset is 1463 # assumed to be in the servlet/portlet itself. 1464 requestWidgetset = self.getApplicationOrSystemProperty( 1465 self.PARAMETER_WIDGETSET, self.DEFAULT_WIDGETSET) 1466 1467 if requestWidgetset is not None: 1468 widgetset = requestWidgetset 1469 widgetsetBasePath = \ 1470 self.getWebApplicationsStaticFileLocation(request) 1471 else: 1472 widgetset = sharedWidgetset 1473 widgetsetBasePath = self.getStaticFilesLocation(request) 1474 1475 widgetset = self.stripSpecialChars(widgetset) 1476 widgetsetFilePath = (widgetsetBasePath 1477 + '/' + self.WIDGETSET_DIRECTORY_PATH + widgetset 1478 + '/' + widgetset + '.nocache.js?' 1479 + str( int(time() * 1000) )) # ms since epoch 1480 1481 # Get system messages 1482 systemMessages = None 1483 # failing to get the system messages is always a problem 1484 try: 1485 systemMessages = self.getSystemMessages() 1486 except SystemMessageException, e: 1487 raise ServletException('CommunicationError!', e) 1488 1489 page.write('<script type=\"text/javascript\">\n') 1490 page.write('//<![CDATA[\n') 1491 page.write(('if(!vaadin || !vaadin.vaadinConfigurations) {\n ' 1492 + 'if(!vaadin) { var vaadin = {}} \n' 1493 + 'vaadin.vaadinConfigurations = {};\n' 1494 + 'if (!vaadin.themesLoaded) ' 1495 + '{ vaadin.themesLoaded = {}; }\n')) 1496 1497 if not self.isProductionMode(): 1498 page.write('vaadin.debug = true;\n') 1499 1500 page.write(('document.write(\'<iframe tabIndex=\"-1\" ' 1501 + 'id=\"__gwt_historyFrame\" ' 1502 + 'style=\"position:absolute;width:0;height:0;border:0;' 1503 + 'overflow:hidden;\" ' 1504 + 'src=\"javascript:false\"></iframe>\');\n')) 1505 1506 page.write('document.write(\"<script language=\'javascript\' ' 1507 + 'src=\'' 1508 + widgetsetFilePath + '\'><\\/script>\");\n}\n') 1509 1510 page.write('vaadin.vaadinConfigurations[\"' 1511 + appId + '\"] = {') 1512 1513 page.write('appUri:\'' + appUrl + '\', ') 1514 1515 if window != application.getMainWindow(): 1516 page.write('windowName: \"' \ 1517 + JsonPaintTarget.escapeJSON(window.getName()) + '\", ') 1518 1519 if self.isStandalone(): 1520 page.write('standalone: true, ') 1521 1522 page.write('themeUri:') 1523 page.write('\"' + themeUri + '\"' if themeUri is not None else 'null') 1524 page.write(', versionInfo : {vaadinVersion:\"') 1525 page.write(self.VERSION) 1526 page.write('\",applicationVersion:\"') 1527 page.write(JsonPaintTarget.escapeJSON(application.getVersion())) 1528 page.write('\"}') 1529 1530 if systemMessages is not None: 1531 # Write the CommunicationError -message to client 1532 caption = systemMessages.getCommunicationErrorCaption() 1533 if caption is not None: 1534 caption = '\"' + JsonPaintTarget.escapeJSON(caption) + '\"' 1535 1536 message = systemMessages.getCommunicationErrorMessage() 1537 if message is not None: 1538 message = '\"' + JsonPaintTarget.escapeJSON(message) + '\"' 1539 1540 url = systemMessages.getCommunicationErrorURL() 1541 if url is not None: 1542 url = '\"' + JsonPaintTarget.escapeJSON(url) + '\"' 1543 else: 1544 url = 'null' 1545 1546 page.write(',\"comErrMsg\": {' + '\"caption\":' 1547 + caption + ',' + '\"message\" : ' + message + ',' 1548 + '\"url\" : ' + url + '}') 1549 1550 # Write the AuthenticationError -message to client 1551 caption = systemMessages.getAuthenticationErrorCaption() 1552 if caption is not None: 1553 caption = '\"' + JsonPaintTarget.escapeJSON(caption) + '\"' 1554 1555 message = systemMessages.getAuthenticationErrorMessage() 1556 if message is not None: 1557 message = '\"' + JsonPaintTarget.escapeJSON(message) + '\"' 1558 1559 url = systemMessages.getAuthenticationErrorURL() 1560 if url is not None: 1561 url = '\"' + JsonPaintTarget.escapeJSON(url) + '\"' 1562 else: 1563 url = 'null' 1564 1565 page.write(',\"authErrMsg\": {' + '\"caption\":' 1566 + caption + ',' + '\"message\" : ' + message 1567 + ',' + '\"url\" : ' + url + '}') 1568 1569 page.write('};\n//]]>\n</script>\n') 1570 1571 if themeName is not None: 1572 # Custom theme's stylesheet, load only once, in different 1573 # script 1574 # tag to be dominate styles injected by widget set 1575 page.write('<script type=\"text/javascript\">\n') 1576 page.write('//<![CDATA[\n') 1577 page.write('if(!vaadin.themesLoaded[\'' + themeName + '\']) {\n') 1578 page.write('var stylesheet = document.createElement(\'link\');\n') 1579 page.write('stylesheet.setAttribute(\'rel\', \'stylesheet\');\n') 1580 page.write('stylesheet.setAttribute(\'type\', \'text/css\');\n') 1581 page.write('stylesheet.setAttribute(\'href\', \'' \ 1582 + themeUri + '/styles.css\');\n') 1583 page.write('document.getElementsByTagName(\'head\')[0]' 1584 '.appendChild(stylesheet);\n') 1585 page.write('vaadin.themesLoaded[\'' \ 1586 + themeName + '\'] = true;\n}\n') 1587 page.write('//]]>\n</script>\n') 1588 1589 # Warn if the widgetset has not been loaded after 15 seconds on 1590 # inactivity 1591 page.write('<script type=\"text/javascript\">\n') 1592 page.write('//<![CDATA[\n') 1593 page.write('setTimeout(\'if (typeof ' 1594 + widgetset.replace('.', '_') 1595 + ' == \"undefined\") {alert(\"Failed to load the widgetset: ' 1596 + widgetsetFilePath 1597 + '\")};\',15000);\n' 1598 + '//]]>\n</script>\n')
1599 1600
1601 - def isStandalone(self):
1602 """@return: true if the served application is considered to be the 1603 only or main content of the host page. E.g. various embedding 1604 solutions should override this to false. 1605 """ 1606 return True
1607 1608
1609 - def writeAjaxPageHtmlBodyStart(self, page, request):
1610 """Method to open the body tag of the html kickstart page. 1611 1612 This method is responsible for closing the head tag and opening 1613 the body tag. 1614 1615 Override this method if you want to add some custom html to the page. 1616 1617 @raise IOException: 1618 """ 1619 page.write('\n</head>\n<body scroll=\"auto\" class=\"' 1620 + ApplicationConnection.GENERATED_BODY_CLASSNAME 1621 + '\">\n')
1622 1623
1624 - def writeAjaxPageHtmlHeader(self, page, title, themeUri, request):
1625 """Method to write the contents of head element in html kickstart page. 1626 1627 Override this method if you want to add some custom html to the header 1628 of the page. 1629 1630 @raise IOException: 1631 """ 1632 page.write('<meta http-equiv=\"Content-Type\" ' 1633 + 'content=\"text/html; charset=utf-8\"/>\n') 1634 1635 context = self.getApplicationContext(self.getSession(request)) 1636 browser = context.getBrowser() 1637 if browser.isIE(): 1638 # Chrome frame in all versions of IE (only if Chrome frame is 1639 # installed) 1640 page.write('<meta http-equiv=\"X-UA-Compatible\" ' 1641 + 'content=\"chrome=1\"/>\n') 1642 1643 page.write('<style type=\"text/css\">' 1644 + 'html, body {height:100%;margin:0;}</style>') 1645 1646 # Add favicon links 1647 page.write('<link rel=\"shortcut icon\" ' 1648 + 'type=\"image/vnd.microsoft.icon\" href=\"' 1649 + themeUri 1650 + '/favicon.ico\" />') 1651 page.write('<link rel=\"icon\" type=\"image/vnd.microsoft.icon\" ' 1652 + 'href=\"' + themeUri + '/favicon.ico\" />') 1653 page.write('<title>' 1654 + self.safeEscapeForHtml(title) 1655 + '</title>')
1656 1657
1658 - def writeAjaxPageHtmlHeadStart(self, page, request):
1659 """Method to write the beginning of the html page. 1660 1661 This method is responsible for writing appropriate doc type 1662 declarations and to open html and head tags. 1663 1664 Override this method if you want to add some custom html to the 1665 very beginning of the page. 1666 1667 @raise IOException: 1668 """ 1669 # write html header 1670 page.write('<!DOCTYPE html PUBLIC \"-//W3C//DTD ' 1671 + 'XHTML 1.0 Transitional//EN\" ' 1672 + '\"http://www.w3.org/TR/xhtml1/' 1673 + 'DTD/xhtml1-transitional.dtd\">\n') 1674 page.write('<html xmlns=\"http://www.w3.org/1999/xhtml\"' 1675 + '>\n<head>\n')
1676 1677
1678 - def setAjaxPageHeaders(self, response):
1679 """Method to set http request headers for the Muntjac kickstart page. 1680 1681 Override this method if you need to customize http headers of the page. 1682 """ 1683 # Window renders are not cacheable 1684 self.setHeader(response, 'Cache-Control', 'no-cache') 1685 self.setHeader(response, 'Pragma', 'no-cache') 1686 self.setHeader(response, 'Expires', '0') 1687 self.setHeader(response, 'Content-Type', 'text/html; charset=UTF-8')
1688 1689
1690 - def getNoScriptMessage(self):
1691 """Returns a message printed for browsers without scripting support 1692 or if browsers scripting support is disabled. 1693 """ 1694 return ('You have to enable javascript in your browser to use an ' 1695 'application built with Muntjac.')
1696 1697
1698 - def getApplicationUrl(self, request):
1699 """Gets the current application URL from request. 1700 1701 @param request: 1702 the HTTP request. 1703 @raise MalformedURLException: 1704 if the application is denied access to the persistent 1705 data store represented by the given URL. 1706 """ 1707 reqURL = 'https://' if self.isSecure(request) else 'http://' 1708 reqHost = request.environ().get('HTTP_HOST') 1709 if reqHost: 1710 reqURL += reqHost 1711 else: 1712 reqURL += self.getServerName(request) 1713 if (self.isSecure(request) and self.getServerPort(request) == 443 1714 or (not self.isSecure(request) 1715 and self.getServerPort(request) == 80)): 1716 pass 1717 else: 1718 reqURL += ':%d' % self.getServerPort(request) 1719 reqURL += self.getRequestUri(request) 1720 1721 # FIXME: implement include requests 1722 if self.getParameter(request, 'javax.servlet.include.servlet_path', 1723 None) is not None: 1724 # this is an include request 1725 servletPath = (self.getParameter(request, 1726 'javax.servlet.include.context_path', None) 1727 + self.getParameter(request, 1728 'javax.servlet.include.servlet_path', None)) 1729 #servletPath = (request.originalContextPath 1730 # + request.originalServletPath()) 1731 else: 1732 servletPath = (self.getContextPath(request) # FIXME: context path 1733 + self.getServletPath(request)) 1734 1735 if len(servletPath) == 0 or servletPath[len(servletPath) - 1] != '/': 1736 servletPath = servletPath + '/' 1737 1738 return urljoin(reqURL, servletPath) # FIXME: urljoin
1739 1740
1741 - def getExistingApplication(self, request, allowSessionCreation):
1742 """Gets the existing application for given request. Looks for 1743 application instance for given request based on the requested URL. 1744 1745 @param request: 1746 the HTTP request. 1747 @param allowSessionCreation: 1748 true if a session should be created if no session 1749 exists, false if no session should be created 1750 @return: Application instance, or null if the URL does not map to 1751 valid application. 1752 @raise MalformedURLException: 1753 if the application is denied access to the persistent 1754 data store represented by the given URL. 1755 @raise SessionExpiredException: 1756 """ 1757 # Ensures that the session is still valid 1758 session = self.getSession(request, allowSessionCreation) 1759 1760 if session is None: 1761 raise SessionExpiredException() 1762 1763 context = self.getApplicationContext(session) 1764 1765 # Gets application list for the session. 1766 applications = context.getApplications() 1767 1768 # Search for the application (using the application URI) from the list 1769 for sessionApplication in applications: 1770 sessionApplicationPath = \ 1771 self.getUrlPath(sessionApplication.getURL()) 1772 requestApplicationPath = \ 1773 self.getUrlPath(self.getApplicationUrl(request)) 1774 1775 if requestApplicationPath == sessionApplicationPath: 1776 # Found a running application 1777 if sessionApplication.isRunning(): 1778 return sessionApplication 1779 1780 # Application has stopped, so remove it before creating a new 1781 # application 1782 self.getApplicationContext( 1783 session).removeApplication(sessionApplication) 1784 break 1785 1786 # Existing application not found 1787 return None
1788 1789
1790 - def endApplication(self, request, response, application):
1791 """Ends the application. 1792 1793 @param request: 1794 the HTTP request. 1795 @param response: 1796 the HTTP response to write to. 1797 @param application: 1798 the application to end. 1799 @raise IOException: 1800 if the writing failed due to input/output error. 1801 """ 1802 logoutUrl = application.getLogoutURL() 1803 1804 if logoutUrl is None: 1805 logoutUrl = application.getURL() 1806 1807 session = self.getSession(request) 1808 if session is not None: 1809 self.getApplicationContext(session).removeApplication(application) 1810 1811 response.sendRedirect(logoutUrl) # FIXME: encodeRedirectURL
1812 1813
1814 - def getApplicationWindow(self, request, applicationManager, application):
1815 """Gets the existing application or create a new one. Get a 1816 window within an application based on the requested URI. 1817 1818 @param request: 1819 the HTTP Request. 1820 @param application: 1821 the Application to query for window. 1822 @return: Window matching the given URI or null if not found. 1823 @raise ServletException: 1824 if an exception has occurred that interferes with the 1825 servlet's normal operation. 1826 """ 1827 # Finds the window where the request is handled 1828 assumedWindow = None 1829 path = self.getRequestPathInfo(request) 1830 1831 # Main window as the URI is empty 1832 if not (path is None or len(path) == 0 or path == '/'): 1833 if path.startswith('/APP/'): 1834 # Use main window for application resources 1835 return application.getMainWindow() 1836 1837 windowName = None 1838 if path[0] == '/': 1839 path = path[1:] 1840 1841 index = path.find('/') 1842 1843 if index < 0: 1844 windowName = path 1845 path = '' 1846 else: 1847 windowName = path[:index] 1848 1849 assumedWindow = application.getWindow(windowName) 1850 1851 return applicationManager.getApplicationWindow(request, self, 1852 application, assumedWindow)
1853 1854
1855 - def getRequestPathInfo(self, request):
1856 """Returns the path info; note that this _can_ be different than 1857 request.getPathInfo(). Examples where this might be useful: 1858 1859 - An application runner servlet that runs different Muntjac 1860 applications based on an identifier. 1861 - Providing a REST interface in the context root, while serving a 1862 Muntjac UI on a sub-URI using only one servlet (e.g. REST on 1863 http://example.com/foo, UI on http://example.com/foo/vaadin) 1864 """ 1865 return self.getPathInfo(request)
1866 1867
1868 - def getResourceLocation(self, theme, resource):
1869 """Gets relative location of a theme resource. 1870 1871 @param theme: 1872 the Theme name. 1873 @param resource: 1874 the Theme resource. 1875 @return: External URI specifying the resource 1876 """ 1877 if self._resourcePath is None: 1878 return resource.getResourceId() 1879 1880 return self._resourcePath + theme + '/' + resource.getResourceId()
1881 1882
1883 - def isRepaintAll(self, request):
1884 return ((self.getParameter(request, self.URL_PARAMETER_REPAINT_ALL, 1885 None) is not None) 1886 and (self.getParameter(request, self.URL_PARAMETER_REPAINT_ALL, 1887 '') == '1'))
1888 1889
1890 - def closeApplication(self, application, session):
1891 if application is None: 1892 return 1893 application.close() 1894 if session is not None: 1895 context = self.getApplicationContext(session) 1896 context.removeApplication(application)
1897 1898
1899 - def getApplicationContext(self, session):
1900 """Gets the application context from an HttpSession. If no context 1901 is currently stored in a session a new context is created and stored 1902 in the session. 1903 1904 @param session: 1905 the HTTP session. 1906 @return: the application context for HttpSession. 1907 """ 1908 # TODO the ApplicationContext.getApplicationContext() should be 1909 # removed and logic moved here. Now overriding context type is 1910 # possible, but the whole creation logic should be here. MT 1101 1911 1912 return WebApplicationContext.getApplicationContext(session, self)
1913 1914
1915 - def createCommunicationManager(self, application):
1916 """Override this method if you need to use a specialized 1917 communication mananger implementation. 1918 1919 @deprecated: Instead of overriding this method, override 1920 L{WebApplicationContext} implementation via 1921 L{getApplicationContext} method and in that customized 1922 implementation return your CommunicationManager in 1923 L{WebApplicationContext.getApplicationManager} 1924 method. 1925 """ 1926 warn("deprecated", DeprecationWarning) 1927 1928 from muntjac.terminal.gwt.server.communication_manager import \ 1929 CommunicationManager 1930 1931 return CommunicationManager(application)
1932 1933 1934 @classmethod
1935 - def safeEscapeForHtml(cls, unsafe):
1936 """Escapes characters to html entities. An exception is made for some 1937 "safe characters" to keep the text somewhat readable. 1938 1939 @return: a safe string to be added inside an html tag 1940 """ 1941 if unsafe is None: 1942 return None 1943 1944 safe = StringIO() 1945 for c in unsafe: 1946 if cls.isSafe(ord(c)): 1947 safe.write(c) 1948 else: 1949 safe.write('&#') 1950 safe.write(ord(c)) 1951 safe.write(';') 1952 1953 result = safe.getvalue() 1954 safe.close() 1955 1956 return result
1957 1958 1959 @classmethod
1960 - def isSafe(cls, c):
1961 # alphanum or A-Z or a-z 1962 return ((c > 47 and c < 58) 1963 or (c > 64 and c < 91) 1964 or (c > 96 and c < 123))
1965
1966 1967 -class ParameterHandlerErrorImpl(ParameterHandlerErrorEvent):
1968 """Implementation of IErrorEvent interface.""" 1969
1970 - def __init__(self, owner, throwable):
1971 self._owner = owner 1972 self._throwable = throwable
1973 1974
1975 - def getThrowable(self):
1976 """Gets the contained throwable. 1977 1978 @see: L{muntjac.terminal.terminal.IErrorEvent.getThrowable()} 1979 """ 1980 return self._throwable
1981 1982
1983 - def getParameterHandler(self):
1984 """Gets the source ParameterHandler. 1985 1986 @see: L{IErrorEvent.getParameterHandler} 1987 """ 1988 return self._owner
1989
1990 1991 -class URIHandlerErrorImpl(URIHandlerErrorEvent):
1992 """Implementation of URIHandler.IErrorEvent interface.""" 1993
1994 - def __init__(self, owner, throwable):
1995 self._owner = owner 1996 self._throwable = throwable
1997 1998
1999 - def getThrowable(self):
2000 """Gets the contained throwable. 2001 2002 @see: L{muntjac.terminal.terminal.IErrorEvent.getThrowable()} 2003 """ 2004 return self._throwable
2005 2006
2007 - def getURIHandler(self):
2008 """Gets the source URIHandler. 2009 2010 @see: L{muntjac.terminal.uri_handler.IErrorEvent.getURIHandler} 2011 """ 2012 return self._owner
2013
2014 2015 -class RequestError(TerminalErrorEvent):
2016
2017 - def __init__(self, throwable):
2018 self._throwable = throwable
2019 2020
2021 - def getThrowable(self):
2022 return self._throwable
2023