note

	description: "[
						Policy-driven handlers.
						Implementers only need to concentrate on creating content.
						]"
	date: "$Date$"
	revision: "$Revision$"

deferred class WSF_SKELETON_HANDLER

inherit

	WSF_URI_TEMPLATE_HANDLER
		redefine
			execute
		end

	WSF_OPTIONS_POLICY

	WSF_PREVIOUS_POLICY

	WSF_CACHING_POLICY

	WSF_METHOD_HELPER_FACTORY

	WSF_SELF_DOCUMENTED_HANDLER

feature {NONE} -- Initialization

	make_with_router (a_router: WSF_ROUTER)
			-- Initialize `router'.
		require
			a_router_attached: a_router /= Void
		do
			router := a_router
		ensure
			router_aliased: router = a_router
		end

feature -- Router

	router: WSF_ROUTER
			-- So that WSF_OPTIONS_POLICY can find the allowed methods

feature -- Execution variables

	Negotiated_language_execution_variable: STRING = "NEGOTIATED_LANGUAGE"
			-- Execution variable set by framework

	Negotiated_charset_execution_variable: STRING = "NEGOTIATED_CHARSET"
			-- Execution variable set by framework

	Negotiated_media_type_execution_variable: STRING = "NEGOTIATED_MEDIA_TYPE"
			-- Execution variable set by framework

	Negotiated_encoding_execution_variable: STRING = "NEGOTIATED_ENCODING"
			-- Execution variable set by framework

	Negotiated_http_header_execution_variable: STRING = "NEGOTIATED_HTTP_HEADER"
			-- Execution variable set by framework

	Request_entity_execution_variable: STRING = "REQUEST_ENTITY"
			-- Execution variable set by framework

	Conflict_check_code_execution_variable: STRING = "CONFLICT_CHECK_CODE"
		-- Execution variable set by framework

	Content_check_code_execution_variable: STRING = "CONTENT_CHECK_CODE"
		-- Execution variable set by framework

	Request_check_code_execution_variable: STRING = "REQUEST_CHECK_CODE"
		-- Execution variable set by framework

feature -- Access

	is_chunking (req: WSF_REQUEST): BOOLEAN
		-- Will the response to `req' using chunked transfer encoding?
		require
			req_attached: req /= Void
		deferred
		end

	includes_response_entity (req: WSF_REQUEST): BOOLEAN
			-- Does the response to `req' include an entity?
			-- Method will be DELETE, OUT, POST or an extension method.
		require
			req_attached: req /= Void
		deferred
		end

	conneg (req: WSF_REQUEST): SERVER_CONTENT_NEGOTIATION
			-- Content negotiation for `req';
			-- This would normally be a once object, ignoring `req'.
		require
			req_attached: req /= Void
		deferred
		end

	mime_types_supported (req: WSF_REQUEST): LIST [STRING]
			-- All values for Accept header that `Current' can serve
		require
			req_attached: req /= Void
		deferred
		ensure
			mime_types_supported_includes_default: Result.has (conneg (req).default_media_type)
		end

	languages_supported (req: WSF_REQUEST): LIST [STRING]
			-- All values for Accept-Language header that `Current' can serve
		require
			req_attached: req /= Void
		deferred
		ensure
			languages_supported_includes_default: Result.has (conneg (req).default_language)
		end

	charsets_supported (req: WSF_REQUEST): LIST [STRING]
			-- All values for Accept-Charset header that `Current' can serve
		require
			req_attached: req /= Void
		deferred
		ensure
			charsets_supported_includes_default: Result.has (conneg (req).default_charset)
		end

	encodings_supported (req: WSF_REQUEST): LIST [STRING]
			-- All values for Accept-Encoding header that `Current' can serve
		require
			req_attached: req /= Void
		deferred
		ensure
			encodings_supported_includes_default: Result.has (conneg (req).default_encoding)
		end

	additional_variant_headers (req: WSF_REQUEST): detachable LIST [STRING]
			-- Header other than Accept, Accept-Language, Accept-Charset and Accept-Encoding,
			--  which might affect the response
		do
		end

	predictable_response (req: WSF_REQUEST): BOOLEAN
			-- Does the response to `req' vary only on the dimensions of ContentType, Language, Charset and Transfer encoding,
			--  plus those named in `additional_variant_headers'?
		do
			Result := True
			-- redefine to return `False', so as to induce a Vary: * header
		end

	allowed_cross_origins (req: WSF_REQUEST): detachable STRING
			-- Value for Access-Control-Allow-Origin header;
			-- If supplied, should be a single URI, or the values "*" or "null".
			-- This is currently supported only for GET requests, and POSTs that functions as GET.
		note
			EIS: "name=specification", "protocol=URI", "src=http://www.w3.org/TR/cors/#http-access-control-allow-origin"
		require
			req_attached: req /= Void
		deferred
		end

	matching_etag (req: WSF_REQUEST; a_etag: READABLE_STRING_GENERAL; a_strong: BOOLEAN): BOOLEAN
			-- Is `a_etag' a match for resource requested in `req'?
			-- If `a_strong' then the strong comparison function must be used.
		require
			req_attached: req /= Void
		deferred
		end

	etag (req: WSF_REQUEST): detachable READABLE_STRING_8
			-- Optional Etag for response entity to `req';
			-- Upto four execution variables may be set on `req':
			-- "NEGOTIATED_MEDIA_TYPE"
			-- "NEGOTIATED_LANGUAGE"
			-- "NEGOTIATED_CHARSET"
			-- "NEGOTIATED_ENCODING"
		require
			req_attached: req /= Void
		deferred
		end

	last_modified (req: WSF_REQUEST): detachable DATE_TIME
			-- When representation of resource selected in `req' was last modified;
			-- SHOULD be set whenever it can reasonably be determined.
		note
			EIS: "name=specification", "protocol=URI", "src=https://tools.ietf.org/html/rfc7232#section-2.2.1"
		require
			req_attached: req /= Void
		deferred
		end

	modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN
			-- Has resource requested in `req' been modified since `a_date_time' (UTC)?
		require
			req_attached: req /= Void
		deferred
		end

	treat_as_moved_permanently (req: WSF_REQUEST): BOOLEAN
			-- Rather than store as a new entity, do we treat it as an existing entity that has been moved?
		require
			req_attached: req /= Void
			put_request: req.is_request_method ({HTTP_REQUEST_METHODS}.method_put)
		do
			-- No. Redefine this if needed.
		end

	allow_post_to_missing_resource (req: WSF_REQUEST): BOOLEAN
			-- The resource named in `req' does not exist, and this is a POST. Do we allow it?
		require
			req_attached: req /= Void
		deferred
		end

feature -- Measurement

	content_length (req: WSF_REQUEST): NATURAL
			-- Length of entity-body of the response to `req'
		require
			req_attached: req /= Void
			not_chunked: not is_chunking (req)
		deferred
		end

feature -- Status report

	finished (req: WSF_REQUEST): BOOLEAN
			-- Has the last chunk been generated for `req'?
		require
			req_attached: req /= Void
			chunked: is_chunking (req)
		deferred
		end


feature -- Documentation

	mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION
			-- Documentation associated with Current handler, in the context of the mapping `m' and methods `a_request_methods'
			--| `m' and `a_request_methods' are useful to produce specific documentation when the handler is used for multiple mapping.
		do
			create Result.make (m)
			Result.add_description (description)
		end

	description: READABLE_STRING_GENERAL
			-- General description for self-generated documentation;
			-- The specific URI templates supported will be described automatically
		deferred
		ensure
			description_attached: Result /= Void
		end

feature -- DELETE

	delete (req: WSF_REQUEST)
			-- Delete resource named in `req' or set an error on `req.error_handler'.
		require
			req_attached: req /= Void
		deferred
		ensure
			error_or_queued_or_deleted: not req.error_handler.has_error implies (delete_queued (req) or deleted (req))
		end

	deleted (req: WSF_REQUEST): BOOLEAN
			-- Has resource named by `req' been deleted?
		require
			req_attached: req /= Void
		do
			if not req.error_handler.has_error then
				Result := True
			end
		ensure
			negative_implication: not Result implies req.error_handler.has_error
		end

	delete_queued (req: WSF_REQUEST): BOOLEAN
			-- Has resource named by `req' been queued for deletion?
		require
			req_attached: req /= Void
		deferred
		ensure
			entity_available: includes_response_entity (req)
		end

feature -- GET/HEAD content

	ensure_content_available (req: WSF_REQUEST)
			-- Commence generation of response text (entity-body) (if not already done in `check_resource_exists').
			-- If not chunked, then this will create the entire entity-body so as to be available
			--  for a subsequent call to `content'.
			-- If chunked, only the first chunk will be made available to `next_chunk'. If chunk extensions
			--  are used, then this will also generate the chunk extension for the first chunk.
			-- Upto four execution variables may be set on `req':
			-- "NEGOTIATED_MEDIA_TYPE"
			-- "NEGOTIATED_LANGUAGE"
			-- "NEGOTIATED_CHARSET"
			-- "NEGOTIATED_ENCODING"
			-- If you support etags, and you have more than one possible representation
			--  for the resource (so that your etag depends upon the particular representation),
			--  then you will probably have already created the response entity in `check_resource_exists'.
		require
			req_attached: req /= Void
			get_or_head_or_delete: req.is_get_head_request_method or req.is_delete_request_method
		deferred
		end

	response_ok (req: WSF_REQUEST): BOOLEAN
			-- Has generation of the response (so-far, if chunked) proceeded witout error?
		require
			req_attached: req /= Void
		do
			Result := not req.error_handler.has_error
		ensure
			last_error_set: Result = not req.error_handler.has_error
		end

	content (req: WSF_REQUEST): READABLE_STRING_8
			-- Non-chunked entity body in response to `req';
			-- Upto four execution variables may be set on `req':
			-- "NEGOTIATED_MEDIA_TYPE"
			-- "NEGOTIATED_LANGUAGE"
			-- "NEGOTIATED_CHARSET"
			-- "NEGOTIATED_ENCODING"
		require
			req_attached: req /= Void
			head_get_or_delete: req.is_get_head_request_method or req.is_delete_request_method
			no_error: response_ok (req)
			not_chunked: not is_chunking (req)
		deferred
		end

	generate_next_chunk (req: WSF_REQUEST)
			-- Prepare next chunk (including optional chunk extension) of entity body in response to `req'.
			-- This is not called for the first chunk.
			-- Upto four execution variables may be set on `req':
			-- "NEGOTIATED_MEDIA_TYPE"
			-- "NEGOTIATED_LANGUAGE"
			-- "NEGOTIATED_CHARSET"
			-- "NEGOTIATED_ENCODING"
		require
			req_attached: req /= Void
			no_error: response_ok (req)
			chunked: is_chunking (req)
		deferred
		end

	next_chunk (req: WSF_REQUEST): TUPLE [a_check: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8]
			-- Next chunk of entity body in response to `req';
			-- The second field of the result is an optional chunk extension.
				-- Four execution variables are set on `req':
			-- "NEGOTIATED_MEDIA_TYPE"
			-- "NEGOTIATED_LANGUAGE"
			-- "NEGOTIATED_CHARSET"
			-- "NEGOTIATED_ENCODING"
		require
			req_attached: req /= Void
			no_error: response_ok (req)
			chunked: is_chunking (req)
		deferred
		end

feature -- PUT/POST

	read_entity (req: WSF_REQUEST)
			-- Read request body and set as `req.execution_variable (Request_entity_execution_variable)'.
		require
			req_attached: req /= Void
		local
			l_body: STRING
		do
			create l_body.make_empty
			req.read_input_data_into (l_body)
			if not l_body.is_empty then
				req.set_execution_variable (Request_entity_execution_variable, l_body)
			end
		end

	is_entity_too_large (req: WSF_REQUEST): BOOLEAN
			-- Is the entity stored in `req.execution_variable (Request_entity_execution_variable)' too large for the application?
		require
			req_attached: req /= Void
		deferred
		end

	check_content_headers (req: WSF_REQUEST)
			-- Check we can support all content headers on request entity.
			-- Set `req.execution_variable (Content_check_code_execution_variable)' to {NATURAL} zero if OK, or 415 or 501 if not.
		require
			req_attached: req /= Void
		deferred
		end

	content_check_code (req: WSF_REQUEST): NATURAL
			-- Code set by `check_content_headers'.
		require
			req_attached: req /= Void
		do
			if attached {NATURAL} req.execution_variable (Content_check_code_execution_variable) as l_code then
				Result := l_code
			end
		end

	create_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
			-- Create new resource in response to a PUT request when `check_resource_exists' returns `False'.
			-- Implementor must set error code of 200 OK or 500 Server Error.
		require
			req_attached: req /= Void
			res_attached: res /= Void
			put_request: req.is_put_request_method
		deferred
		end

	append_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
			-- Create new resource in response to a POST request.
			-- Implementor must set error code of 200 OK or 204 No Content or 303 See Other or 500 Server Error.
		require
			req_attached: req /= Void
			res_attached: res /= Void
			post_request: req.is_post_request_method
		deferred
		end

	check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE)
			-- Check to see if updating the resource is problematic due to the current state of the resource.
			-- Set `req.execution_variable (Conflict_check_code_execution_variable)' to {NATURAL} zero if OK, or 409 if not.
			-- In the latter case, write the full error response to `res'.
		require
			req_attached: req /= Void
			res_attached: res /= Void
		deferred
		end

	conflict_check_code (req: WSF_REQUEST): NATURAL
			-- Code set by `check_conflict'.
		require
			req_attached: req /= Void
		do
			if attached {NATURAL} req.execution_variable (Conflict_check_code_execution_variable) as l_code then
				Result := l_code
			end
		end

	check_request (req: WSF_REQUEST; res: WSF_RESPONSE)
			-- Check that the request entity is a valid request.
			-- The entity is available as `req.execution_variable (Request_entity_execution_variable)'.
			-- Set `req.execution_variable (Request_check_code_execution_variable)' to {NATURAL} zero if OK, or 400 if not.
			-- In the latter case, write the full error response to `res'.
		require
			req_attached: req /= Void
			res_attached: res /= Void
			put_or_post: req.is_put_post_request_method
		deferred
		end

	request_check_code (req: WSF_REQUEST): NATURAL
			-- Code set by `check_request'.
		require
			req_attached: req /= Void
		do
			if attached {NATURAL} req.execution_variable (Request_check_code_execution_variable) as l_code then
				Result := l_code
			end
		end

	update_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
			-- Perform the update requested in `req'.
			-- Write a response to `res' with a code of 204 or 500.
		require
			req_attached: req /= Void
			res_attached: res /= Void
			put_request: req.is_put_request_method
		deferred
		end

feature -- Execution

	execute (req: WSF_REQUEST; res: WSF_RESPONSE)
			-- <Precursor>
		do
			check
				known_method: router.allowed_methods_for_request (req).has (req.request_method)
				not_trace: not req.is_request_method ({HTTP_REQUEST_METHODS}.method_trace)
				not_connect: not req.is_request_method ({HTTP_REQUEST_METHODS}.method_connect)
			end
			if req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) then
				execute_options (req, res, router)
			else
				if req.is_request_method ({HTTP_REQUEST_METHODS}.method_put) and attached req.http_content_range then
					handle_invalid_content_range (res)
				elseif attached new_method_helper (req.request_method) as l_helper then
					execute_method (req, res, l_helper)
				else
					handle_internal_server_error (res)
				end
			end
		end

	execute_method (req: WSF_REQUEST; res: WSF_RESPONSE; a_helper: WSF_METHOD_HELPER)
			-- Write response to `req' into `res', using `a_helper' as a logic helper.
		require
			req_attached: req /= Void
			res_attached: res /= Void
			a_helper_attached: a_helper /= Void
		do
			a_helper.handle_content_negotiation (req, res, Current)
			if res.status_code /= {HTTP_STATUS_CODE}.Not_acceptable then
				check_resource_exists (req, a_helper)
				if a_helper.resource_exists then
					a_helper.execute_existing_resource (req, res, Current)
				elseif req.error_handler.has_error then
					a_helper.write_error_response (req, res)
				else
					if attached req.http_if_match as l_if_match and then l_if_match.same_string ("*") then
						a_helper.handle_precondition_failed (req, res)
					else
						a_helper.execute_new_resource (req, res, Current)
					end
				end
			end
		end

	check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER)
			-- Call `a_helper.set_resource_exists' to indicate that `req.path_translated'
			--  is the name of an existing resource.
			-- Upto four execution variables may be set on `req':
			-- "NEGOTIATED_MEDIA_TYPE"
			-- "NEGOTIATED_LANGUAGE"
			-- "NEGOTIATED_CHARSET"
			-- "NEGOTIATED_ENCODING"
			-- If you support etags, and you have more than one possible representation
			--  for the resource (so that your etag depends upon the particular representation),
			--  then you will probably need to create the response entity at this point, rather
			--  than in `ensure_content_available'.
		require
			req_attached: req /= Void
			a_helper_attached: a_helper /= Void
		deferred
		end

feature {NONE} -- Implementation

	handle_invalid_content_range (res: WSF_RESPONSE)
			-- Write "Bad Request" response to `res' for Content-Range header present in PUT request.
		require
			res_attached: res /= Void
		local
			h: HTTP_HEADER
			m: STRING_8
		do
			create h.make
			h.put_content_type_text_plain
			m := "Content-Range header present in PUT request"
			h.put_content_length (m.count)
			h.put_current_date
			res.set_status_code ({HTTP_STATUS_CODE}.bad_request)
			res.put_header_lines (h)
			res.put_string (m)
		ensure
			response_status_is_set: res.status_is_set
			status_is_service_unavailable: res.status_code = {HTTP_STATUS_CODE}.internal_server_error
			body_sent: res.message_committed and then res.transfered_content_length > 0
		end

	handle_internal_server_error (res: WSF_RESPONSE)
			-- Write "Internal Server Error" response to `res'.
		require
			res_attached: res /= Void
		local
			h: HTTP_HEADER
			m: STRING_8
		do
			create h.make
			h.put_content_type_text_plain
			m := "Server failed to handle request properly"
			h.put_content_length (m.count)
			h.put_current_date
			res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error)
			res.put_header_lines (h)
			res.put_string (m)
		ensure
			response_status_is_set: res.status_is_set
			status_is_service_unavailable: res.status_code = {HTTP_STATUS_CODE}.internal_server_error
			body_sent: res.message_committed and then res.transfered_content_length > 0
		end

note
	copyright: "2011-2014, Colin Adams, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
	license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
	source: "[
			Eiffel Software
			5949 Hollister Ave., Goleta, CA 93117 USA
			Telephone 805-685-1006, Fax 805-685-6869
			Website http://www.eiffel.com
			Customer support http://support.eiffel.com
		]"
end
