A
download Session.rb
Language: Ruby
LOC: 138
Project Info
IOWA
Server: RubyForge
Type: cvs
...Forge\i\iowa\iowa\iowa\src\
   Application.rb
   ApplicationStats.rb
   Association.rb
   BindingsParser.rb
   Client.rb
   Component.rb
   ComponentProxy.rb
   config.rb
   Context.rb
   DbPool.rb
   DynamicElements.rb
   Element.rb
   Form.rb
   HTTPServer.rb
   ISAAC.rb
   KeyValueCoding.rb
   LRUCache.rb
   PageStore.rb
   read_multipart.rb
   Request.rb
   SciTE.properties
   Session.rb
   SessionStats.rb
   SessionStore.rb
   Tag.rb
   TemplateParser.rb
   WEBrickServlet.rb

require 'thread'
require 'log4r'
include Log4r
require 'iowa/PageStore'
require 'iowa/SessionStats'

module Iowa

	# Container for an inline generated resource.
	
	class Resource
		attr_accessor :body, :content_type
		
		def initialize(body,content_type='application/data')
			@body = body
			@content_type = content_type
		end
	end
	
	# Custom exception which is thrown if a request does not have a request ID.
	
	class IgnoreRequest < Exception; end
	
	# Custom exception which is thrown if a request asks for a page which has
	# expired from the cache.
	
	class PageExpired < Exception; end
	
	# Represents a session between the application and the user.
	
	class Session
	
		# The default number of pages to cache.  It'd be nice if this were
		# easily configurable somehow, so apps could finetune their own
		# caching behaviors.
	
		@@cachedPages = 10
		@@cacheTTL = nil
		
		# Default delay between the presentation of the page expired message
		# (see this below) and the redirect to the top of the application.
	
		@@expiryDelay = 1
	
		# Class variable which holds the class of a session.
	
		@@sessionClass = Session
	
		# Define an accessor to make the context object accessible to the
		# application's code.
	
		attr_accessor :context, :application, :currentPage, :requestCount, :pages,
			:resourceCount
	
		# Set the @cachedPages class variable.

		def Session.cachedPages
			@@cachedPages
		end

		def Session.cachedPages=(val)
			@@cachedPages = val
		end

		def Session.cacheTTL
			@@cacheTTL
		end

		def Session.cacheTTL=(val)
			@@cacheTTL = val
		end
	
		# Constructor to return a new instance of @@sessionClass.
	
		def Session.newSession(*args)
			@@sessionClass.new(*args)
		end
	
		def Session.inherited(subclass)
			@@sessionClass = subclass
		end
	
		# Initialize the state of the session.
	
		def initialize
			mylog = Logger['iowa_log']
			@currentPage = nil
			@requestCount = 'a'
			@resourceCount = 'a'
			@resources = {}
			@resources_by_component = {}
			@lock = Mutex.new
			@creation_time = Time.now
			@pages = PageStore.new(@@cachedPages,@@cacheTTL)
			@pages.add_finalizer(@resources,@resources_by_component) {|key,obj,resources,resources_by_component|
				if resources_by_component.has_key? key
					resources_by_component[key].each do |res_id|
						resources.delete res_id
					end
					resources_by_component.delete key
				end
			}
			@statistics = Iowa::SessionStats.new(@pages)
		end
	
		def statistics
			@statistics
		end
	
		# Handles the current request from the browser.  Essentially all that
		# is happening is thread synchronizations along with some tests to make
		# sure that all the data necessary to handle the request exists.
		# Then the call to handleRequest gets passed on to the object representing
		# the current page.
	
		def handleRequest(context)
			@statistics.hit
			@access_time = Time.now
			mylog = Logger['iowa_log']
			if @lock.locked?
				raise "concurrent session access for #{self}"
			end
			@lock.synchronize do
				begin
					context.session = self
	
					begin
						requestPage = @currentPage
						@context = context
	
						raise IgnoreRequest unless context.requestID
						return handle_resource(context) if @resources.has_key? "#{context.requestID}.#{context.actionID}"
						raise PageExpired unless @pages.include? context.requestID
						requestPage = @pages[context.requestID]
	
						if @requestCount != context.requestID
							requestPage.handleBacktrack
						end
	
						@currentPage = requestPage.dup
						
						@application.reloadModified(@currentPage.class.to_s)
						
						@currentPage.handleRequest(context)
						nextPage = context.invokeAction
						@currentPage = nextPage if nextPage
	
						@requestCount = @requestCount.next
	
					rescue IgnoreRequest
						@currentPage = requestPage
					end
	
					handleResponse(context)
				rescue PageExpired
					expired(context)
				end
			end
		end
	
		# Invokes the handleResponse() method on the object representing the page
		# and updates the page cache.
	
		def handleResponse(context)
			unless @currentPage
				@currentPage = Component.pageNamed("Main", self)
			end
					
			@application.reloadModified(@currentPage.class.to_s,true)
			context.requestID = @requestCount
			@currentPage.handleResponse(context)
	
			@pages[@requestCount] = @currentPage
		end

		# If one passes into resource_url content of some sort plus an optional
		# content type (default is application/data if not specified), it will
		# return a URL that can be used to access that content.  If, on the
		# other hand, one passes an arbitrary set of arguments and a block,
		# a url will be returned that, when accesed, will cause the block to
		# be executed.  If the block returns an instance of Iowa::Resource,
		# the content in the resource will be returned as a result of calling
		# that URL, with the content type specified in the resource object.
		# Any other return value will have that value returned as the result
		# of calling the URL, with a content type of 'application/data'.
		# The resources are tied to the page that created them, so they are
		# available until the page expires.  Once the page expires, the
		# resource is deleted as well.
		def resource_url(*args,&block)
			if block_given?
				resource = [block,args]
			else
				#args[0] == body, args[1] == content type
				resource = Iowa::Resource.new(args[0], args[1])
			end
			resourceID = "r_#{@requestCount}.#{@resourceCount}"
			@resources[resourceID] = resource
			@resources_by_component[@context.requestID] ||= []
			@resources_by_component[@context.requestID].push resourceID
			@resourceCount = @resourceCount.next
			return "#{@context.sessionURL}.#{resourceID}"
		end
		alias :resourceUrl :resource_url
		
		def handle_resource(context)			
			r = @resources["#{context.requestID}.#{context.actionID}"]
			resource = r.kind_of?(Array) ? r[0].call(*r[1]) : r
			if resource.kind_of? Iowa::Resource
				context.response << resource.body
				context.request.content_type = resource.content_type
			else
				context.response << resource.to_s
				context.request.content_type = 'application/data'
			end
		end
		
		private
	
		# Returns an explanation that the page being requested has expired, then
		# issues a redirect to the head of the app.  It'd be neat if there were
		# some easy way to configure the contents of this page for an app.

		def expired(context)
			context.response << "<html><head><meta http-equiv=REFRESH content='#{@@expiryDelay}; URL=#{context.sessionURL}'></head><body><b>That page has expired.<p>You are being forwarded to your <a href='#{context.sessionURL}'>current point</a> in the session.  Please continue from there.</b></body></html>"
		end
	end

end

About Koders | Resources | Downloads | Support | Black Duck | Terms of Service | DMCA | Privacy Policy | Contact Us