Class: SWS::Application

Inherits:
Object
  • Object
show all
Defined in:
lib/sws/application.rb

Overview

Main class of each SWS application. Can be accessed as a singleton object. Contains global data (session independent) and perform fundamental request/response/exception handling. Defines request handlers.

Constant Summary collapse

@@instance =

Singleton instance of an application

nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeApplication

Creates new singleton Application object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/sws/application.rb', line 82

def initialize ()

	if ( @@instance != nil )
		raise "Cannot create Application instance if one exists!"
	else
		@@instance = self
	end

	@request_count = 0
	@frameworks = Hash.new
	
	load_config_file()
	
	setup_request_handlers()
	setup_component_cache()
	setup_session_cleaner()			

end

Instance Attribute Details

#adaptorObject (readonly)

Adaptor object - determines the type of interface the application uses (eg. Adaptor, FastCGIAdaptor etc)



28
29
30
# File 'lib/sws/application.rb', line 28

def adaptor
  @adaptor
end

#component_pathsObject (readonly)

Paths in which all files of components will be searched for



61
62
63
# File 'lib/sws/application.rb', line 61

def component_paths
  @component_paths
end

#component_request_handler_keyObject (readonly)

Request handler key for component requests



49
50
51
# File 'lib/sws/application.rb', line 49

def component_request_handler_key
  @component_request_handler_key
end

#default_component_class_nameObject (readonly)

Name of the subclass of Component that will be used as default for serving requests (eg. for default DirectAction or “/”). Defaults to “Main”



68
69
70
# File 'lib/sws/application.rb', line 68

def default_component_class_name
  @default_component_class_name
end

#default_direct_action_classObject (readonly)

Name of the subclass of DirectAction that will be used to serve DirectAction requests with no class specified. Defaults to DirectAction



72
73
74
# File 'lib/sws/application.rb', line 72

def default_direct_action_class
  @default_direct_action_class
end

#default_encodingObject

Default encoding for components



24
25
26
# File 'lib/sws/application.rb', line 24

def default_encoding
  @default_encoding
end

#direct_action_request_handler_keyObject (readonly)

Request handler key for direct action requests



55
56
57
# File 'lib/sws/application.rb', line 55

def direct_action_request_handler_key
  @direct_action_request_handler_key
end

#exception_componentObject (readonly)

Component class used to generate exception page



79
80
81
# File 'lib/sws/application.rb', line 79

def exception_component
  @exception_component
end

#page_name_request_handler_keyObject (readonly)

Request handler key for page by name requests



52
53
54
# File 'lib/sws/application.rb', line 52

def page_name_request_handler_key
  @page_name_request_handler_key
end

#request_handlersObject (readonly)

Handlers for different types of requests. There are following types of requests:

  • component request: URL /cp/11324134 - request for existing component registered

in @pages hash - the number is component id, ‘cp’ - request handler key

  • page by name request: URL /pn/132443243/pageName - request for component

by its name - ‘pn’ is request handler key, number is previous component id (it is necessary so that one cannot access page directly by just writing eg. /pn/43532322/pageName)

  • direct action: URL /da/className/actionName - like direct action in WO.

Again, ‘da’ is request handler key, actionName is self-descripting

  • resource request: URL /rs/frameworkName/resourceName - request for

application resource, eg. image or CSS file. You can also provide custom request handlers - just add them to Key - request handler key (eg. ‘pn’, ‘da’), value - method handling this type of request.



46
47
48
# File 'lib/sws/application.rb', line 46

def request_handlers
  @request_handlers
end

#resource_pathsObject (readonly)

Paths in which resources will be searched for



64
65
66
# File 'lib/sws/application.rb', line 64

def resource_paths
  @resource_paths
end

#resource_request_handler_keyObject (readonly)

Request handler key for resource requests



58
59
60
# File 'lib/sws/application.rb', line 58

def resource_request_handler_key
  @resource_request_handler_key
end

#session_classObject (readonly)

Name of the subclass of Session that will be used for storing sessions. Defaults to Session



76
77
78
# File 'lib/sws/application.rb', line 76

def session_class
  @session_class
end

Class Method Details

.instanceObject

Returns the singleton instance of the application



230
231
232
# File 'lib/sws/application.rb', line 230

def Application.instance ()
	return @@instance
end

Instance Method Details

#get_component(component_name) ⇒ Object

Returns ComponentFiles struct for given component name. Usually called only once per component class, so it is a good place to require its ruby file.



569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/sws/application.rb', line 569

def get_component( component_name )

	# Find component path
	path = @config[CONFIG_COMPONENTS][component_name]

	unless ( path )
		framework = @frameworks.values.find { |framework|	framework.components[component_name] }
		if ( framework )
			path = File.join( framework.path,framework.components[component_name] )
		else
			raise( "Component #{component_name} was not found in application or	frameworks" )
		end
	end		

	# Retrieve component files. For components in nested namespaces only the
	# last element of the name is used.
	file = File.join( path, component_name.split( /::/ ).last )

	if ( FileTest.file?( file+".rb" ) ) then ruby_file = file+".rb" end
	if ( FileTest.file?( file+".sws" ) ) then sws_file = file+".sws" end
	if ( FileTest.file?( file+".html" ) ) then html_file = file+".html" end
	if ( FileTest.file?( file+".api" ) ) then api_file = file+".api" end

	if ( ruby_file )
		require( ruby_file )
		klass = SWS.get_class( component_name )
		return ComponentInfo.new( klass, sws_file, html_file, api_file )
	end
			
	raise "Cannot find component files for component #{component_name}"
	
end

#handle_component_request(request, url_content) ⇒ Object

Handles component requests



425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/sws/application.rb', line 425

def handle_component_request ( request,url_content )

	# check if url_content =~ /^component_data(/action)
	if ( md = /^([^\/]*)\/+(.*)$/.match( url_content ) )

		component_data = md[1]
		action_object_id = md[2].to_i

	else	#no "/" after component_id

		component_data = url_content

	end

	component_id, request_number = component_data.split( /\./ ).collect { |el| el.to_i }
	
	$log_sws_component.debug( "Request number: #{request_number}" )
	$log_sws_component.debug( "Got component id: #{component_id}" )
	component = request.session.get_from_cache( component_id )
	$log_sws_component.debug( "Retrieved component id: #{component.object_id}" )
	unless ( component && request_number )
		if request.session.old?( component_id ) # Backtracked too far
			return handle_too_far_backtrack( request )
		else # new session or random URL	- redirect to default component
			# We don't care about any parameters
			request.erase_content()
			component = Component.create( @default_component_class_name,request )
			request_number = nil
		end
	else
	
		# TODO: make it possible to present a custom error page on backtrack (it
		# would require holding ids of all old components in session)
		if ( action_object_id )	
			#action_object_id contains id of object, whose action should be performed
			#right now it can only be a Hyperlink or a Form
			action_object = component.action_components[ action_object_id ] 
			if ( action_object )
				# We don't call the action immediately - only mark it as enabled and
				# it will be called during call_action phase of request-response loop
				action_object.enable_action()
			else
				component = Component.create( @default_component_class_name,request )
			end					
		end
	end
	
	$log_sws_component.debug( "Component id: #{component.object_id}, request_number #{request_number}" )
	return component.process_request( request, request_number )
	
end

#handle_direct_action_request(request, url_content) ⇒ Object

Handles direct action requests



508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# File 'lib/sws/application.rb', line 508

def handle_direct_action_request ( request,url_content )
	
	class_name, method_name = url_content.split( /\/+/,2 )
	if ( method_name )
		klass = SWS.get_class( class_name + "Action" )
	else
		klass = @default_direct_action_class
		method_name = class_name
	end
	unless ( klass && klass.ancestors.include?( DirectAction ) )
		raise TypeError.new( "Direct action class name for url #{url_content} does not inherit from SWS::DirectAction" )
	end
	
	direct_action = klass.new( request )
				
	new_component, response = direct_action.action_for_name( method_name )

	return [new_component, response]
	
end

#handle_exception(request, exception) ⇒ Object

Exception handling method. If you want to customize error handling, just override this one.



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/sws/application.rb', line 266

def handle_exception ( request,exception )
	
	$log_sws_request.warn( "Exception in application loop: #{exception}" )
	$log_sws_request.warn( "Backtrace: #{exception.backtrace.join("\n")}" )

	begin
		
		exception_page = Component.create( @exception_component, request )
		exception_page.exception = exception
		component,response = exception_page.process_request( request )
		
	rescue Exception => exception #just in case there is an error in SWS itself

		$log_sws_component.warn( "Exception in exception component #{@exception_component}: #{exception}" )
		$log_sws_component.warn( "Backtrace: #{exception.backtrace.join("\n")}" )
		
		response = Response.new( request,"500" )			
		response.cookies << request.session.to_cookie
		response.headers["Content-type"] = "text/html;charset=#{@default_encoding}"
		response.headers["Pragma"] = "no-cache"
		response.headers["Expires"] = "0"
		response.headers["Cache-control"] = "private, no-cache, no-store, must-revalidate, max-age = 0"

		response << "<HTML><BODY><H1>Exception raised!</H1>\n"
		response << "<H3>#{exception.class}:#{exception}</H3>\n"
		response << "Backtrace: <BR> #{exception.backtrace.join("<BR>\n")}"
		response << "</BODY></HTML>\n"
		
	end

	return response

end

#handle_page_name_request(request, url_content) ⇒ Object

Handles page name requests



479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/sws/application.rb', line 479

def handle_page_name_request ( request,url_content )

	if ( md = /^(-?\d+)\/(.+)/.match( url_content ) )

		previous_component = request.session.get_from_cache( md[1].to_i )
		unless ( previous_component )
			
			request.erase_content()
			component = Component.create( @default_component_class_name,request )

			return component.process_request( request )
			
		else
			
			next_component_name = md[2]
			component = Component.create( next_component_name, request, next_component_name)
					
			return component.process_request( request )
			
		end

	else
		raise( "Malformed url for pn type request" )
	end

end

#handle_refuse_session(request) ⇒ Object

Called when new session is to be refused. Default implementation return a simplistic info page. Note that this method probably shouldn’t return a component, as a component may require session to work properly.



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/sws/application.rb', line 321

def handle_refuse_session ( request )

	response = Response.new( request ,"503" ) 
	response.headers["Content-type"] = "text/html;charset=#{@default_encoding}"
	response.headers["Pragma"] = "no-cache"
	response.headers["Expires"] = "0"
	response.headers["Cache-control"] = "private, no-cache, no-store, must-revalidate, max-age = 0"

	response << "<HTML><BODY><H1>Session refused!</H1>\n"
	response << "<H3>Session refused due to application overload. Please try again later.</H3>"
	response << "</BODY></HTML>\n"
	
	return response

end

#handle_request(request) ⇒ Object

Takes the request and calls one of request_handlers basing on request handler key contained in URL. Also performs session retrieval/creation.



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# File 'lib/sws/application.rb', line 340

def handle_request ( request )

	# hack for requests for favicon if it doesn't exist
	# TODO: it blocks ALL favicon.ico requests, but should block only if
	# favicon.ico is not provided
	# TODO: or we should omit this issue after implementation of
	# handling more than one current component 
	if ( request.query_string =~ /favicon.ico$/ || request.path =~ /favicon.ico$/ )
		return Response.new( request, "404 Not Found" )
	end

	session = request.session 
	if ( session == nil )
		
		if ( @session_class.sessions.size < @max_sessions )
			
			session = @session_class.new
			request.session = session
			
		else	# Too many sessions
			
			if ( @refuse_sessions )
				return handle_refuse_session( request )
			else
				# Delete Least Recently Used session
				session_to_delete = @session_class.sessions.values.inject do |session1,session2|
					session1.last_access_time < session2.last_access_time ? session1 : session2
				end
				
				@session_class.delete_session( session_to_delete )
				session = @session_class.new
				request.session = session
			end

		end
		
	end

	session.last_access_time = Time.now

	#strip leading /'s
	path = request.path.sub( /^\/+/,"" )

	#path =~ ^/key/url_content$		
	if ( md = /^([^\/]*)\/+(.*)$/.match( path ) )			
		
		request_handler_key = md[1]
		#TODO: rename it: its just the url without request handler key
		#I assume the query string is already stripped
		url_content = md[2]
		request_handler = method( @request_handlers[request_handler_key] )		
	else
		#call default request handler
		request_handler = method( @request_handlers[path] )
		url_content = path
	end

	begin

		component,response = request_handler.call( request,url_content )
		# The resource request handler does not return component

		# That means that we only want to send response without any component.
		# Variable component is used to return response. Look at
		# SWS::Component#process_request for more. 
		if( response == nil )
			return component
		end
		
		if ( component )
			request.session.add_to_cache( component )
		end
		return response
		
	rescue Exception => exception
		
		return handle_exception( request,exception )
		
	end
	

end

#handle_resource_request(request, url_content) ⇒ Object

Handles resource requests



531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
# File 'lib/sws/application.rb', line 531

def handle_resource_request ( request,url_content )
	
	response = Response.new( request )

	framework_name,resource_name = url_content.split( /\/+/,2 )
	if ( framework_name == 'app' )
		resource = @config[CONFIG_RESOURCES][resource_name]
	else
		framework = @frameworks[framework_name]
		resource = framework.resources[resource_name]
	end
	
	unless ( resource )
		raise( "Cannot find resource #{resource_name} in framework #{framework}" )
	end
	
	response.headers["Content-type"] = resource["mime-type"]
	if ( framework )
		path = File.join( framework.path, resource["filename"] )
	else
		path = resource["filename"]
	end
	unless( File.exists?( path ) )
		raise( "File #{path} does not exist" )
	end

	file = File.open( path )
	response << file.read
	file.close()

	return nil,response
	
end

#handle_too_far_backtrack(request) ⇒ Object



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/sws/application.rb', line 301

def handle_too_far_backtrack ( request )
	
		# TODO: not sure about the status
		response = Response.new( request,"200 SWS" )
		response.cookies << request.session.to_cookie
		response.headers["Content-type"] = "text/html;charset=#{@default_encoding}"
		response.headers["Pragma"] = "no-cache"
		response.headers["Expires"] = "0"
		response.headers["Cache-control"] = "private, no-cache, no-store, must-revalidate, max-age = 0"

		# TODO: allow the user to enter the application again
		response << "<HTML><BODY><H1>You backtracked too far</H1>\n"
		response << "</BODY></HTML>\n"
		return nil,response

end

#request_loopObject

Main request loop of the application. Reads Request objects from the adaptor and passes them to #handle_request method



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/sws/application.rb', line 246

def request_loop ()
	
	@adaptor.each_request do |request|

		@request_count += 1
	
		# The response has to be returned from the block - handle request returns one
		response = nil
		@session_cleaner_mutex.synchronize {
			response = handle_request( request )
		}				
		response
		
	end
	
end

#runObject

Starts the application (starts the adaptor and goes into #request_loop)



236
237
238
239
240
241
# File 'lib/sws/application.rb', line 236

def run ()
	
	@adaptor.run()
	request_loop()
	
end