diff --git a/examples/restbucksCRUD/README-compilation.txt b/examples/rest/restbucks_CRUD/README-compilation.txt
similarity index 100%
rename from examples/restbucksCRUD/README-compilation.txt
rename to examples/rest/restbucks_CRUD/README-compilation.txt
diff --git a/examples/restbucksCRUD/client/README.txt b/examples/rest/restbucks_CRUD/client/README.txt
similarity index 100%
rename from examples/restbucksCRUD/client/README.txt
rename to examples/rest/restbucks_CRUD/client/README.txt
diff --git a/examples/restbucksCRUD/client/client.ecf b/examples/rest/restbucks_CRUD/client/client.ecf
similarity index 75%
rename from examples/restbucksCRUD/client/client.ecf
rename to examples/rest/restbucks_CRUD/client/client.ecf
index c8feff93..3314de3e 100644
--- a/examples/restbucksCRUD/client/client.ecf
+++ b/examples/rest/restbucks_CRUD/client/client.ecf
@@ -7,13 +7,13 @@
/.svn$/EIFGENs$
-
-
-
-
+
+
+
diff --git a/examples/rest/restbucks_CRUD/client/src/restbuck_client.e b/examples/rest/restbucks_CRUD/client/src/restbuck_client.e
new file mode 100644
index 00000000..7f13eb4f
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/client/src/restbuck_client.e
@@ -0,0 +1,177 @@
+note
+ description : "Objects that ..."
+ author : "$Author$"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ RESTBUCK_CLIENT
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ -- Initialize `Current'.
+ local
+ h: NET_HTTP_CLIENT
+ sess: HTTP_CLIENT_SESSION
+ resp : detachable HTTP_CLIENT_RESPONSE
+ l_location : detachable READABLE_STRING_8
+ body : STRING
+ do
+ create h
+ sess := h.new_session ("http://127.0.0.1:" + server_port.out)
+-- Uncomment the following 2 lines, if you use fiddler2 web debugging tool
+-- sess.set_is_debug (True)
+-- sess.set_proxy ("127.0.0.1", 8888)
+
+ -- Create Order
+ print ("> Create Order %N")
+ resp := create_order (sess)
+ display_response (resp)
+
+
+ -- Read the Order
+ l_location := resp.header ("Location")
+ if l_location /= Void then
+ print ("> Read Order from " + l_location + " %N")
+ resp := read_order (sess, l_location)
+ display_response (resp)
+ else
+ print ("> Previous order creation failed, no location returned!%N")
+ end
+
+ -- Update the Order
+ if resp /= Void and then attached resp.body as l_body then
+ body := l_body.as_string_8
+ body.replace_substring_all ("takeAway", "in Shop")
+ print ("> Update Order: change location from [takeAway] to [in Shop] %N")
+ resp := update_order (sess, l_location, body)
+ display_response (resp)
+ end
+
+ -- Pay the Order
+ if resp /= Void and then attached resp.body as l_body then
+ body := l_body.as_string_8
+ body.replace_substring_all ("%"status%":%"submitted%"", "%"status%":%"paid%"")
+ print ("> Pay Order: should trigger validation error!%N")
+ resp := update_order (sess, l_location, body)
+ display_response (resp)
+ end
+ end
+
+ update_order (sess: HTTP_CLIENT_SESSION; uri: detachable READABLE_STRING_8; a_body: STRING): detachable HTTP_CLIENT_RESPONSE
+ local
+ context : HTTP_CLIENT_REQUEST_CONTEXT
+ do
+ if uri /= Void then
+ sess.set_base_url (uri)
+ create context.make
+ context.headers.put ("application/json", "Content-Type")
+ Result := sess.put ("", context, a_body )
+ end
+ end
+
+ read_order (sess: HTTP_CLIENT_SESSION; uri: detachable READABLE_STRING_8): detachable HTTP_CLIENT_RESPONSE
+ do
+ if uri /= Void then
+ sess.set_base_url (uri)
+ Result := sess.get ("", Void)
+ end
+ end
+
+ create_order (sess: HTTP_CLIENT_SESSION) : HTTP_CLIENT_RESPONSE
+ local
+ s: READABLE_STRING_8
+ j: JSON_PARSER
+ context : HTTP_CLIENT_REQUEST_CONTEXT
+ do
+ s := "[
+ {
+ "location":"takeAway",
+ "items":[
+ {
+ "name":"Late",
+ "option":"skim",
+ "size":"Small",
+ "quantity":1
+ }
+ ]
+ }
+ ]"
+
+ create context.make
+ context.headers.put ("application/json", "Content-Type")
+ Result := sess.post ("/order", context, s)
+ end
+
+ display_response (resp: detachable HTTP_CLIENT_RESPONSE)
+ do
+ io.error.put_string (create {STRING}.make_filled ('-', 40))
+ io.error.put_new_line
+ if resp = Void then
+ io.error.put_string ("ERROR: No response~%N")
+ else
+ if resp.error_occurred and attached resp.error_message as err_msg then
+ io.error.put_string ("[ERROR] ")
+ io.error.put_string (err_msg)
+ io.error.put_new_line
+ end
+
+ -- Display response status and header
+ io.error.put_string ("Status code: " + resp.status.out + "%N")
+ across
+ resp.headers as l_headers
+ loop
+ io.error.put_string (l_headers.item.name)
+ io.error.put_string (":")
+ io.error.put_string (l_headers.item.value)
+ io.error.put_new_line
+ end
+
+ -- Show the Response body
+ if not resp.error_occurred and attached resp.body as m then
+ io.error.put_string (m)
+ io.error.put_new_line
+ end
+ end
+ io.error.put_string (create {STRING}.make_filled ('-', 40))
+ io.error.put_new_line
+ end
+
+feature {NONE} -- Implementation
+
+ server_port: INTEGER
+ local
+ f: PLAIN_TEXT_FILE
+ p: PATH
+ s: STRING
+ do
+ create p.make_current
+ p := p.extended ("..").extended ("server.ini")
+ create f.make_with_path (p)
+ if f.exists and then f.is_access_readable then
+ f.open_read
+ from
+ until
+ f.exhausted or f.end_of_file or Result > 0
+ loop
+ f.read_line
+ s := f.last_string
+ if s.starts_with_general ("port=") then
+ s.remove_head (5)
+ if s.is_integer then
+ Result := s.to_integer
+ end
+ end
+ end
+ f.close
+ end
+ if Result <= 0 then
+ Result := 80
+ end
+ end
+
+end
diff --git a/examples/restbucksCRUD/license.lic b/examples/rest/restbucks_CRUD/license.lic
similarity index 100%
rename from examples/restbucksCRUD/license.lic
rename to examples/rest/restbucks_CRUD/license.lic
diff --git a/examples/restbucksCRUD/readme.md b/examples/rest/restbucks_CRUD/readme.md
similarity index 65%
rename from examples/restbucksCRUD/readme.md
rename to examples/rest/restbucks_CRUD/readme.md
index 613b81cd..0e90a937 100644
--- a/examples/restbucksCRUD/readme.md
+++ b/examples/rest/restbucks_CRUD/readme.md
@@ -6,100 +6,68 @@ the HTTP protocol as an application protocol instead of a transport protocol.
Restbuck Protocol
-----------------
-
-
Verb
URI or template
Use
-
POST
/order
Create a new order, and upon success, receive a Locationheader specifying the new order's URI.
-
GET
/order/{orderId}
Request the current state of the order specified by the URI.
-
PUT
/order/{orderId}
Update an order at the given URI with new information, providing the full representation.
-
DELETE
/order/{orderId}
Logically remove the order identified by the given URI.
-
+* Method `POST` with URI `/order` : Create a new order, and upon success, receive a Locationheader specifying the new order's URI.
+* Method `GET` with URI-template `/order/{orderId}` : Request the current state of the order specified by the URI.
+* Method `PUT` with URI-template `/order/{orderId}` : Update an order at the given URI with new information, providing the full representation.
+* Method `DELETE` with URI-tempalte `/order/{orderId}` : Logically remove the order identified by the given URI.
Resource Represenation
----------------------
-The previous tables shows a contrat, the URI or URI template, allows us to indentify resources, now we will chose a
-representacion, for this particular case we will use JSON.
+The previous tables shows a contrat, the URI or URI-template, allows us to indentify resources, now we will chose a
+representation, for this particular case we will use JSON.
-Note:
-1. *A resource can have multiple URIs*.
-2. *A resource can have multiple Representations*.
+Note:
+1. *A resource can have multiple URIs*.
+2. *A resource can have multiple Representations*.
RESTBUCKS_SERVER
----------------
This class implement the main entry of our REST CRUD service, we are using a default connector (Standalone Connector,
using a WebServer written in Eiffel).
-We are inheriting from URI_TEMPLATE_ROUTED_SERVICE, this allows us to map our service contrat, as is shown in the previous
-table, the mapping is defined in the feature setup_router, this also show that the class ORDER_HANDLER will be encharge
-of to handle different type of request to the ORDER resource.
+We are inheriting from `WSF_ROUTED_SKELETON_EXECUTION`, this allows us to map our service contrat, as is shown in the previous
+table, the mapping is defined in the feature `setup_router`, this also show that the class `ORDER_HANDLER` will be in charge
+ of handling different types of request to the ORDER resource.
+```
+ class RESTBUCKS_SERVER_EXECUTION
- class
- RESTBUCKS_SERVER
-
inherit
- ANY
-
- URI_TEMPLATE_ROUTED_SERVICE
-
- DEFAULT_SERVICE
- -- Here we are using a default connector using the default Standalone Connector,
- -- but it's possible to use other connector (CGI or FCGI).
-
+ WSF_ROUTED_SKELETON_EXECUTION
+ undefine
+ requires_proxy
+ end
+
+ WSF_NO_PROXY_POLICY
+
+
+ SHARED_RESTBUCKS_API
+
create
make
-
+
feature {NONE} -- Initialization
-
- make
- -- Initialize the router (this will have the request handler and
- -- their context).
- do
- initialize_router
- make_and_launch
- end
-
- create_router
- do
- create router.make (2)
- end
-
+
setup_router
local
- order_handler: ORDER_HANDLER [REQUEST_URI_TEMPLATE_HANDLER_CONTEXT]
+ doc: WSF_ROUTER_SELF_DOCUMENTATION_HANDLER
do
- create order_handler
- router.map_with_request_methods ("/order", order_handler, <<"POST">>)
- router.map_with_request_methods ("/order/{orderid}", order_handler, <<"GET", "DELETE", "PUT">>)
+ setup_order_handler (router)
+
+ create doc.make_hidden (router)
+ router.handle ("/api/doc", doc, router.methods_GET)
end
-
- feature -- Execution
-
- execute_default (req: WSF_REQUEST; res: WSF_RESPONSE)
- -- I'm using this method to handle the method not allowed response
- -- in the case that the given uri does not have a corresponding http method
- -- to handle it.
+
+ setup_order_handler (a_router: WSF_ROUTER)
local
- h : HTTP_HEADER
- l_description : STRING
- l_api_doc : STRING
+ order_handler: ORDER_HANDLER
do
- if req.content_length_value > 0 then
- req.input.read_string (req.content_length_value.as_integer_32)
- end
- create h.make
- h.put_status ({HTTP_STATUS_CODE}.method_not_allowed)
- h.put_content_type_text_plain
- l_api_doc := "%NPlease check the API%NURI:/order METHOD: POST%NURI:/order/{orderid} METHOD: GET, PUT, DELETE%N"
- l_description := req.request_method + req.request_uri + " is not allowed" + "%N" + l_api_doc
- h.put_content_length (l_description.count)
- h.put_current_date
- res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed)
- res.write_header_text (h.string)
- res.write_string (l_description)
+ create order_handler.make ("orderid", a_router)
+ router.handle ("/order", order_handler, router.methods_POST)
+ router.handle ("/order/{orderid}", order_handler, router.methods_GET + router.methods_DELETE + router.methods_PUT)
end
-
+
end
-
-
+```
How to Create an order with POST
--------------------------------
@@ -111,7 +79,7 @@ If the request POST is SUCCESS, the server will create the order and will respon
if the request POST is not SUCCESS, the server will response with
400 BAD REQUEST, the client send a bad request or
500 INTERNAL_SERVER_ERROR, when the server can deliver the request.
-
+```
POST /order HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
@@ -134,9 +102,10 @@ if the request POST is not SUCCESS, the server will response with
}
]
}
+```
Response success
-
+```
HTTP/1.1 201 Created
Status 201 Created
Content-Type application/json
@@ -154,6 +123,7 @@ Response success
"option" : "skim"
} ]
}
+```
note:
curl -vv http://localhost:9090/order -H "Content-Type: application/json" -d "{\"location\":\"takeAway\",\"items\":[{\"name\":\"Late\",\"option\":\"skim\",\"size\":\"Small\",\"quantity\":1}]}" -X POST
@@ -166,6 +136,7 @@ If the GET request is SUCCESS, we response with 200 OK, and a representation of
If the GET request is not SUCCESS, we response with 404 Resource not found
If is a Conditional GET and the resource does not change we send a 304, Resource not modifed
+```
GET /order/1 HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
@@ -174,8 +145,10 @@ If is a Conditional GET and the resource does not change we send a 304, Resource
Accept-Language: es-419,es;q=0.8,en;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
If-None-Match: 6542EF270D91D3EAF39CFB382E4CEBA7
+```
Response
+```
HTTP/1.1 200 OK
Status 200 OK
@@ -194,6 +167,7 @@ Response
"option" : "skim"
} ]
}
+```
note:
curl -vv http://localhost:9090/order/1
@@ -210,8 +184,7 @@ If the request is a Conditional PUT, and it does not mat we response 415, precon
Suposse that we had created an Order with the values shown in the _How to create an order with POST_
But we change our decision and we want to stay in the shop.
-
-
+```
PUT /order/1 HTTP/1.1
Content-Length: 122
Content-Type: application/json; charset=UTF-8
@@ -229,10 +202,10 @@ But we change our decision and we want to stay in the shop.
"option" : "skim"
} ]
}
-
+```
Response success
-
+```
HTTP/1.1 200 OK
Status 200 OK
Content-Type application/json
@@ -250,6 +223,7 @@ Response success
"option" : "skim"
} ]
}
+```
How to Delete an order with DELETE
----------------------------------
@@ -259,28 +233,34 @@ Here we use DELETE to cancel an order, if that order is in state where it can st
405 if consumer and service's view of the resouce state is inconsisent
500 if we have an internal server error
-
+```
DELETE /order/1 HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
+```
Response success
+```
HTTP/1.1 204 No Content
Status 204 No Content
Content-Type application/json
Date FRI,09 DEC 2011 21:10:51.00 GMT
-
+```
+
If we want to check that the resource does not exist anymore we can try to retrieve a GET /order/1 and we will receive a
404 No Found
+```
GET /order/1 HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
+```
Response
+```
HTTP/1.1 404 Not Found
Status 404 Not Found
@@ -289,9 +269,10 @@ Response
Date FRI,09 DEC 2011 21:14:17.79 GMT
The following resource/order/1 is not found
-
+```
References
----------
1. [How to get a cup of coffe](http://www.infoq.com/articles/webber-rest-workflow)
-2. [Rest in Practice] (http://restinpractice.com/default.aspx)
+2. [Rest in Practice](http://restinpractice.com/default.aspx)
+
diff --git a/examples/restbucksCRUD/restbucks-safe.ecf b/examples/rest/restbucks_CRUD/restbucks.ecf
similarity index 67%
rename from examples/restbucksCRUD/restbucks-safe.ecf
rename to examples/rest/restbucks_CRUD/restbucks.ecf
index 78e1e6aa..a9c1ab2f 100644
--- a/examples/restbucksCRUD/restbucks-safe.ecf
+++ b/examples/rest/restbucks_CRUD/restbucks.ecf
@@ -9,19 +9,19 @@
-
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -41,10 +41,10 @@
-
+
- /resource$
+ /resource/order_handler.e$
diff --git a/examples/restbucksCRUD/server.ini b/examples/rest/restbucks_CRUD/server.ini
similarity index 100%
rename from examples/restbucksCRUD/server.ini
rename to examples/rest/restbucks_CRUD/server.ini
diff --git a/examples/rest/restbucks_CRUD/src/conversion/order_json_serialization.e b/examples/rest/restbucks_CRUD/src/conversion/order_json_serialization.e
new file mode 100644
index 00000000..9e908534
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/src/conversion/order_json_serialization.e
@@ -0,0 +1,167 @@
+note
+ description: "Summary description for {ORDER_JSON_SERIALIZATION}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ ORDER_JSON_SERIALIZATION
+
+inherit
+ JSON_SERIALIZER
+
+ JSON_DESERIALIZER
+
+feature -- Conversion
+
+ to_json (obj: detachable ANY; ctx: JSON_SERIALIZER_CONTEXT): JSON_VALUE
+ -- JSON value representing the JSON serialization of Eiffel value `obj', in the eventual context `ctx'.
+ local
+ j_order: JSON_OBJECT
+ j_item: JSON_OBJECT
+ ja: JSON_ARRAY
+ do
+ has_error := False
+ if attached {ORDER} obj as l_order then
+ create j_order.make_with_capacity (4)
+ Result := j_order
+ j_order.put_string (l_order.id, id_key)
+ if attached l_order.location as loc then
+ j_order.put_string (loc, location_key)
+ end
+ j_order.put_string (l_order.status, status_key)
+ if attached l_order.items as l_items and then not l_items.is_empty then
+ create ja.make (l_items.count)
+ j_order.put (ja, items_key)
+ across
+ l_items as ic
+ loop
+ if attached {ORDER_ITEM} ic.item as l_item then
+ create j_item.make_with_capacity (4)
+ j_item.put_string (l_item.name, name_key)
+ j_item.put_string (l_item.size, size_key)
+ j_item.put_integer (l_item.quantity, quantity_key)
+ j_item.put_string (l_item.option, option_key)
+ ja.extend (j_item)
+ end
+ end
+ end
+ else
+ create {JSON_NULL} Result
+ has_error := True
+ end
+ end
+
+ from_json (a_json: detachable JSON_VALUE; ctx: JSON_DESERIALIZER_CONTEXT; a_type: detachable TYPE [detachable ANY]): detachable ORDER
+ -- .
+ local
+ l_status: detachable STRING_32
+ q: NATURAL_8
+ is_valid_from_json: BOOLEAN
+ l_name, l_size, l_option: detachable READABLE_STRING_32
+ do
+ has_error := False
+ is_valid_from_json := True
+ if attached {JSON_OBJECT} a_json as jobj then
+ -- Either new order (i.e no id and no status)
+ -- or an existing order with `id` and `status` (could be Void, thus use default).
+ if attached {JSON_STRING} jobj.item (status_key) as j_status then
+ l_status := j_status.unescaped_string_32
+ end
+ if
+ attached {JSON_STRING} jobj.item (id_key) as j_id
+ then
+ -- Note: the id has to be valid string 8 value!
+ create Result.make (j_id.unescaped_string_8, l_status)
+ elseif attached {JSON_NUMBER} jobj.item (id_key) as j_id then
+ -- Be flexible and accept json number as id.
+ create Result.make (j_id.integer_64_item.out, l_status)
+ else
+ create Result.make_empty
+ if l_status /= Void then
+ Result.set_status (l_status)
+ end
+ end
+ if attached {JSON_STRING} jobj.item (location_key) as j_location then
+ Result.set_location (j_location.unescaped_string_32)
+ end
+ if attached {JSON_ARRAY} jobj.item (items_key) as j_items then
+ across
+ j_items as ic
+ loop
+ if attached {JSON_OBJECT} ic.item as j_item then
+ if
+ attached {JSON_NUMBER} j_item.item (quantity_key) as j_quantity and then
+ j_quantity.integer_64_item < {NATURAL_8}.Max_value
+ then
+ q := j_quantity.integer_64_item.to_natural_8
+ else
+ q := 0
+ end
+ if
+ attached {JSON_STRING} j_item.item (name_key) as j_name and then
+ attached {JSON_STRING} j_item.item (size_key) as j_size and then
+ attached {JSON_STRING} j_item.item (option_key) as j_option
+ then
+ l_name := j_name.unescaped_string_32
+ l_size := j_size.unescaped_string_32
+ l_option := j_option.unescaped_string_32
+ if is_valid_item_customization (l_name, l_size, l_option, q) then
+ Result.add_item (create {ORDER_ITEM}.make (l_name, l_size, l_option, q))
+ else
+ is_valid_from_json := False
+ end
+ else
+ is_valid_from_json := False
+ end
+ end
+ end
+ end
+ if not is_valid_from_json or Result.items.is_empty then
+ Result := Void
+ end
+ else
+ is_valid_from_json := a_json = Void or else attached {JSON_NULL} a_json
+ Result := Void
+ end
+ has_error := not is_valid_from_json
+ end
+
+ has_error: BOOLEAN
+ -- Error occurred during last `from_json` or `to_json` execution.
+
+feature {NONE} -- Implementation
+
+ id_key: STRING = "id"
+
+ location_key: STRING = "location"
+
+ status_key: STRING = "status"
+
+ items_key: STRING = "items"
+
+ name_key: STRING = "name"
+
+ size_key: STRING = "size"
+
+ quantity_key: STRING = "quantity"
+
+ option_key: STRING = "option"
+
+feature -- Validation
+
+ is_valid_item_customization (name: READABLE_STRING_GENERAL; size: READABLE_STRING_GENERAL; option: READABLE_STRING_GENERAL; quantity: NATURAL_8): BOOLEAN
+ local
+ ic: ORDER_ITEM_VALIDATION
+ do
+ create ic
+ Result := ic.is_valid_coffee_type (name) and
+ ic.is_valid_milk_type (option) and
+ ic.is_valid_size_option (size) and
+ quantity > 0
+ end
+
+note
+ copyright: "2011-2017, Javier Velilla and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+
+end
diff --git a/examples/rest/restbucks_CRUD/src/database/basic_database.e b/examples/rest/restbucks_CRUD/src/database/basic_database.e
new file mode 100644
index 00000000..8dc05789
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/src/database/basic_database.e
@@ -0,0 +1,44 @@
+note
+ description: "[
+ Basic database for simple example.
+
+ (no concurrency access control, ...)
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ BASIC_DATABASE
+
+feature -- Access
+
+ count_of (a_entry_type: TYPE [detachable ANY]): INTEGER
+ deferred
+ end
+
+ has (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL): BOOLEAN
+ -- Has entry of type `a_entry_type` associated with id `a_id`?
+ deferred
+ end
+
+ item (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL): detachable ANY
+ deferred
+ end
+
+ save (a_entry_type: TYPE [detachable ANY]; a_entry: detachable ANY; cl_entry_id: CELL [detachable READABLE_STRING_GENERAL])
+ deferred
+ ensure
+ has_id: cl_entry_id.item /= Void
+ end
+
+ delete (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL)
+ require
+ has_item: has (a_entry_type, a_id)
+ deferred
+ ensure
+ has_not_item: not has (a_entry_type, a_id)
+ end
+note
+ copyright: "2011-2017, Javier Velilla and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+end
diff --git a/examples/rest/restbucks_CRUD/src/database/basic_json_fs_database.e b/examples/rest/restbucks_CRUD/src/database/basic_json_fs_database.e
new file mode 100644
index 00000000..3bca11aa
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/src/database/basic_json_fs_database.e
@@ -0,0 +1,177 @@
+note
+ description: "[
+ Basic database for simple example using JSON files.
+
+ (no concurrency access control, ...)
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ BASIC_JSON_FS_DATABASE
+
+inherit
+ BASIC_DATABASE
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_location: PATH)
+ local
+ d: DIRECTORY
+ do
+ location := a_location
+ create serialization
+ ensure_directory_exists (a_location)
+ end
+
+feature -- Access serialization
+
+ serialization: JSON_SERIALIZATION
+
+feature -- Access
+
+ location: PATH
+
+feature -- Access
+
+ count_of (a_entry_type: TYPE [detachable ANY]): INTEGER
+ local
+ d: DIRECTORY
+ do
+ create d.make_with_path (location.extended (entry_type_name (a_entry_type)))
+ if d.exists then
+ across
+ d.entries as ic
+ loop
+ if attached ic.item.extension as e and then e.is_case_insensitive_equal ("json") then
+ Result := Result + 1
+ end
+ end
+ end
+ end
+
+ has (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL): BOOLEAN
+ -- Has entry of type `a_entry_type` associated with id `a_id`?
+ local
+ fut: FILE_UTILITIES
+ do
+ Result := fut.file_path_exists (entry_path (a_entry_type, a_id))
+ end
+
+ item (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL): detachable ANY
+ local
+ f: RAW_FILE
+ s: STRING
+ do
+ create f.make_with_path (entry_path (a_entry_type, a_id))
+ if f.exists then
+ create s.make (f.count)
+ f.open_read
+ from
+ until
+ f.exhausted or f.end_of_file
+ loop
+ f.read_stream (1_024)
+ s.append (f.last_string)
+ end
+ f.close
+ Result := serialization.from_json_string (s, a_entry_type)
+ end
+ end
+
+ save (a_entry_type: TYPE [detachable ANY]; a_entry: detachable ANY; cl_entry_id: CELL [detachable READABLE_STRING_GENERAL])
+ local
+ f: RAW_FILE
+ l_id: detachable READABLE_STRING_GENERAL
+ do
+ l_id := cl_entry_id.item
+ if l_id = Void then
+ l_id := next_identifier (a_entry_type)
+ cl_entry_id.replace (l_id)
+ end
+ create f.make_with_path (entry_path (a_entry_type, l_id))
+ ensure_directory_exists (f.path.parent)
+ f.open_write
+ f.put_string (serialization.to_json (a_entry).representation)
+ f.close
+ end
+
+ delete (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL)
+ local
+ f: RAW_FILE
+ do
+ create f.make_with_path (entry_path (a_entry_type, a_id))
+ if f.exists and then f.is_access_writable then
+ f.delete
+ end
+ end
+
+feature {NONE} -- Implementation
+
+ ensure_directory_exists (dn: PATH)
+ local
+ d: DIRECTORY
+ do
+ create d.make_with_path (dn)
+ if not d.exists then
+ d.recursive_create_dir
+ end
+ end
+
+ entry_path (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL): PATH
+ do
+ Result := location.extended (entry_type_name (a_entry_type)).extended (a_id).appended_with_extension ("json")
+ end
+
+ entry_type_name (a_entry_type: TYPE [detachable ANY]): STRING
+ do
+ Result := a_entry_type.name.as_lower
+ Result.prune_all ('!')
+ end
+
+ last_id_file_path (a_entry_type: TYPE [detachable ANY]): PATH
+ do
+ Result := location.extended (entry_type_name (a_entry_type)).extended ("last-id")
+ end
+
+ next_identifier (a_entry_type: TYPE [detachable ANY]): STRING_8
+ local
+ i: NATURAL_64
+ f: RAW_FILE
+ s: STRING
+ do
+ create f.make_with_path (last_id_file_path (a_entry_type))
+ ensure_directory_exists (f.path.parent)
+ if f.exists then
+ create s.make (f.count)
+ f.open_read
+ f.read_line
+ s := f.last_string
+ f.close
+ if s.is_natural_64 then
+ i := s.to_natural_64
+ end
+ end
+ from
+ i := i + 1
+ Result := i.out
+ until
+ not has (a_entry_type, Result)
+ loop
+ i := i + 1
+ Result := i.out
+ end
+ f.open_write
+ f.put_string (Result)
+ f.close
+ end
+
+invariant
+
+note
+ copyright: "2011-2017, Javier Velilla and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+end
diff --git a/examples/rest/restbucks_CRUD/src/database/basic_memory_database.e b/examples/rest/restbucks_CRUD/src/database/basic_memory_database.e
new file mode 100644
index 00000000..1600cbd5
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/src/database/basic_memory_database.e
@@ -0,0 +1,102 @@
+note
+ description: "[
+ Basic database for simple example based on memory.
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ BASIC_MEMORY_DATABASE
+
+inherit
+ BASIC_DATABASE
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ do
+ create collections.make (0)
+ end
+
+ collections: HASH_TABLE [STRING_TABLE [detachable ANY], TYPE [detachable ANY]]
+
+feature -- Access
+
+ count_of (a_entry_type: TYPE [detachable ANY]): INTEGER
+ do
+ if attached collections.item (a_entry_type) as tb then
+ Result := tb.count
+ end
+ end
+
+ has (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL): BOOLEAN
+ -- Has entry of type `a_entry_type` associated with id `a_id`?
+ do
+ if attached collections.item (a_entry_type) as tb then
+ Result := tb.has_key (a_id)
+ end
+ end
+
+ item (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL): detachable ANY
+ do
+ if attached collections.item (a_entry_type) as tb then
+ Result := tb.item (a_id)
+ end
+ end
+
+ save (a_entry_type: TYPE [detachable ANY]; a_entry: detachable ANY; cl_entry_id: CELL [detachable READABLE_STRING_GENERAL])
+ local
+ tb: detachable STRING_TABLE [detachable ANY]
+ l_id: detachable READABLE_STRING_GENERAL
+ do
+ tb := collections.item (a_entry_type)
+ if tb = Void then
+ create tb.make (100)
+ collections.force (tb, a_entry_type)
+ end
+ l_id := cl_entry_id.item
+ if l_id = Void then
+ l_id := next_identifier (a_entry_type)
+ cl_entry_id.replace (l_id)
+ end
+ tb.force (a_entry, l_id)
+ end
+
+ delete (a_entry_type: TYPE [detachable ANY]; a_id: READABLE_STRING_GENERAL)
+ do
+ if attached collections.item (a_entry_type) as tb then
+ tb.remove (a_id)
+ end
+ end
+
+feature {NONE} -- Implementation
+
+ next_identifier (a_entry_type: TYPE [detachable ANY]): STRING_8
+ local
+ i: INTEGER
+ f: RAW_FILE
+ s: STRING
+ do
+ if attached collections.item (a_entry_type) as tb then
+ i := tb.count
+ else
+ i := 0
+ end
+ from
+ i := i + 1
+ Result := i.out
+ until
+ not has (a_entry_type, Result)
+ loop
+ i := i + 1
+ Result := i.out
+ end
+ end
+
+note
+ copyright: "2011-2017, Javier Velilla and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+end
diff --git a/examples/rest/restbucks_CRUD/src/database/restbucks_api.e b/examples/rest/restbucks_CRUD/src/database/restbucks_api.e
new file mode 100644
index 00000000..376435ff
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/src/database/restbucks_api.e
@@ -0,0 +1,173 @@
+note
+ description: "Summary description for {RESTBUCKS_API}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ RESTBUCKS_API
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ local
+ db: BASIC_JSON_FS_DATABASE
+ do
+ create db.make (database_path)
+ db.serialization.register (create {ORDER_JSON_SERIALIZATION}, {ORDER})
+ database := db
+ end
+
+feature -- Access
+
+ orders_count: INTEGER
+ -- Number of existing orders.
+ do
+ Result := database.count_of ({ORDER})
+ end
+
+ has_order (a_id: READABLE_STRING_GENERAL): BOOLEAN
+ do
+ Result := database.has ({ORDER}, a_id)
+ end
+
+ order (a_id: READABLE_STRING_GENERAL): detachable ORDER
+ do
+ if attached {ORDER} database.item ({ORDER}, a_id) as o then
+ Result := o
+ end
+ end
+
+feature -- Element change
+
+ submit_order (a_order: ORDER)
+ -- Submit new order `a_order`.
+ require
+ no_id: not a_order.has_id
+ do
+ a_order.mark_submitted
+ save_order (a_order)
+ end
+
+ save_order (a_order: ORDER)
+ local
+ cl: CELL [detachable READABLE_STRING_GENERAL]
+ do
+ a_order.add_revision
+ if a_order.has_id then
+ create cl.put (a_order.id)
+ else
+ create cl.put (Void)
+ end
+ database.save ({ORDER}, a_order, cl)
+ if attached cl.item as l_new_id then
+ if l_new_id.is_valid_as_string_8 then
+ a_order.set_id (l_new_id.to_string_8)
+ else
+ check valid_id: False end
+ end
+ end
+ ensure
+ has_id: a_order.has_id
+ incremented_revision: a_order.revision > old (a_order.revision)
+ end
+
+ delete_order (a_order: ORDER)
+ do
+ database.delete ({ORDER}, a_order.id)
+ end
+
+feature -- Access: order status
+
+ is_valid_status_state (a_status: STRING): BOOLEAN
+ -- Is `a_status' a valid order state
+ do
+ Result := Order_states.has (a_status.as_lower)
+ end
+
+ Order_states : ARRAY [STRING]
+ -- List of valid status states
+ once
+ Result := <<
+ status_unset,
+ status_submitted,
+ status_pay, status_payed,
+ status_cancel, status_canceled,
+ status_prepare, status_prepared,
+ status_deliver,
+ status_completed
+ >>
+ Result.compare_objects
+ end
+
+ is_valid_transition (a_order: ORDER; a_new_status: STRING): BOOLEAN
+ -- Is transition from `a_order.status` to `a_new_status` valid for `a_order`?
+ local
+ l_order_status: READABLE_STRING_GENERAL
+ l_new_status: STRING
+ do
+ l_order_status := a_order.status
+ l_new_status := a_new_status.as_lower
+ if l_order_status.same_string (l_new_status) then
+ -- Same status is valid, if it is a valid status
+ Result := is_valid_status_state (l_new_status)
+ else
+ if l_order_status.same_string (status_submitted) then
+ Result := l_new_status.same_string (status_pay)
+ or l_new_status.same_string (status_cancel)
+ elseif l_order_status.same_string (status_pay) then
+ Result := l_new_status.same_string (status_payed)
+ elseif l_order_status.same_string (status_cancel) then
+ Result := l_new_status.same_string (status_canceled)
+ elseif l_order_status.same_string (status_payed) then
+ Result := l_new_status.same_string (status_prepared)
+ elseif l_order_status.same_string (status_prepared) then
+ Result := l_new_status.same_string (status_deliver)
+ elseif l_order_status.same_string (status_deliver) then
+ Result := l_new_status.same_string (status_completed)
+ end
+ end
+ end
+
+ is_state_valid_to_update (a_status : STRING) : BOOLEAN
+ -- Is it possible to update order with status `a_status`?
+ do
+ if
+ a_status.same_string (status_submitted)
+ or else a_status.same_string (status_pay)
+ or else a_status.same_string (status_payed)
+ then
+ Result := True
+ end
+ end
+
+feature -- Constants: order status
+
+ status_unset: STRING = "unset"
+ status_submitted: STRING = "submitted"
+ status_pay: STRING = "pay"
+ status_payed: STRING = "payed"
+ status_cancel: STRING = "cancel"
+ status_canceled: STRING = "canceled"
+ status_prepare: STRING = "prepare"
+ status_prepared: STRING = "prepared"
+ status_deliver: STRING = "deliver"
+ status_completed: STRING = "completed"
+
+feature {NONE} -- Access
+
+ database: BASIC_DATABASE
+
+feature {NONE} -- Implementation
+
+ database_path: PATH
+ once
+ create Result.make_from_string ("db")
+ end
+
+;note
+ copyright: "2011-2017, Javier Velilla and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+end
diff --git a/examples/rest/restbucks_CRUD/src/database/shared_restbucks_api.e b/examples/rest/restbucks_CRUD/src/database/shared_restbucks_api.e
new file mode 100644
index 00000000..62c8bc40
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/src/database/shared_restbucks_api.e
@@ -0,0 +1,61 @@
+note
+ description: "Summary description for {SHARED_RESTBUCKS_API}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ SHARED_RESTBUCKS_API
+
+feature -- Access: bridget to api
+
+ has_order (a_id: READABLE_STRING_GENERAL): BOOLEAN
+ do
+ Result := api.has_order (a_id)
+ end
+
+ order (a_id: READABLE_STRING_GENERAL): detachable ORDER
+ do
+ Result := api.order (a_id)
+ end
+
+feature -- Element change
+
+ submit_order (a_order: ORDER)
+ -- Submit new order `a_order`.
+ require
+ no_id: not a_order.has_id
+ do
+ api.submit_order (a_order)
+ ensure
+ a_order.has_id
+ a_order.is_submitted
+ end
+
+ update_order (a_order: ORDER)
+ -- Update the order to the repository
+ require
+ a_order.has_id
+ do
+ api.save_order (a_order)
+ ensure
+ a_order_with_id: a_order.has_id
+ end
+
+ delete_order (a_order: ORDER)
+ require
+ a_order_with_id: a_order.has_id
+ do
+ api.delete_order (a_order)
+ end
+
+feature -- Access
+
+ api: RESTBUCKS_API
+ once
+ create Result.make
+ end
+
+note
+ copyright: "2011-2017, Javier Velilla and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+end
diff --git a/examples/rest/restbucks_CRUD/src/domain/order.e b/examples/rest/restbucks_CRUD/src/domain/order.e
new file mode 100644
index 00000000..88ddb584
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/src/domain/order.e
@@ -0,0 +1,135 @@
+note
+ description: "Summary description for {ORDER}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ ORDER
+
+create
+ make,
+ make_empty
+
+feature -- Initialization
+
+ make_empty
+ do
+ create {ARRAYED_LIST [ORDER_ITEM]} items.make (10)
+ revision := 0
+ set_id ("")
+ status := {RESTBUCKS_API}.status_unset
+ end
+
+ make (a_id: READABLE_STRING_8; a_status: detachable READABLE_STRING_GENERAL)
+ do
+ make_empty
+ set_id (a_id)
+ if a_status /= Void then
+ set_status (a_status)
+ else
+ check status.same_string_general ({RESTBUCKS_API}.status_unset) end
+ end
+ end
+
+feature -- Access
+
+ id: IMMUTABLE_STRING_8
+
+ location: detachable STRING_32
+
+ status: STRING_32
+ -- Status of the order, see {RESTBUCKS_API}.Order_states
+
+ revision: INTEGER
+
+ items: LIST [ORDER_ITEM]
+
+feature -- Status report
+
+ has_id: BOOLEAN
+ -- Has valid identifier `id`?
+ do
+ Result := not id.is_whitespace and not id.is_case_insensitive_equal ("0")
+ end
+
+ is_submitted: BOOLEAN
+ do
+ Result := status.is_case_insensitive_equal_general ({RESTBUCKS_API}.status_submitted)
+ end
+
+feature -- element change
+
+ set_id (a_id: READABLE_STRING_8)
+ do
+ if attached {IMMUTABLE_STRING_8} a_id as l_id then
+ id := l_id
+ else
+ create id.make_from_string (a_id)
+ end
+ ensure
+ id_assigned : a_id.same_string (id)
+ end
+
+ set_location (a_location: detachable READABLE_STRING_GENERAL)
+ do
+ if a_location = Void then
+ location := Void
+ else
+ create location.make_from_string_general (a_location)
+ end
+ ensure
+ location_assigned: (a_location = Void implies location = Void)
+ or (a_location /= Void implies attached location as loc and then a_location.same_string (loc))
+ end
+
+ mark_submitted
+ do
+ status := {RESTBUCKS_API}.status_submitted
+ end
+
+ set_status (a_status: READABLE_STRING_GENERAL)
+ do
+ create status.make_from_string_general (a_status)
+ ensure
+ status_assigned : a_status.same_string (status)
+ end
+
+ add_item (a_item: ORDER_ITEM)
+ require
+ valid_item: a_item /= Void
+ do
+ items.force (a_item)
+ ensure
+ has_item : items.has (a_item)
+ end
+
+ add_revision
+ do
+ revision := revision + 1
+ ensure
+ revision_incremented : old revision + 1 = revision
+ end
+
+feature -- Report
+
+ hash_code: INTEGER_32
+ -- Hash code value
+ do
+ from
+ items.start
+ Result := items.item.hash_code
+ until
+ items.off
+ loop
+ Result:= ((Result \\ 8388593) |<< 8) + items.item.hash_code
+ items.forth
+ end
+ if items.count > 1 then
+ Result := Result \\ items.count
+ end
+ end
+
+note
+ copyright: "2011-2017, Javier Velilla and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+end
diff --git a/examples/rest/restbucks_CRUD/src/domain/order_item.e b/examples/rest/restbucks_CRUD/src/domain/order_item.e
new file mode 100644
index 00000000..ffaf52bf
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/src/domain/order_item.e
@@ -0,0 +1,93 @@
+note
+ description: "Summary description for {ORDER_ITEM}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ ORDER_ITEM
+
+inherit
+ ORDER_ITEM_VALIDATION
+
+create
+ make
+
+feature -- Initialization
+
+ make (a_name: STRING_32; a_size: STRING_32; a_option: STRING_32; a_quantity: NATURAL_8)
+ do
+ set_name (a_name)
+ set_size (a_size)
+ set_option (a_option)
+ set_quantity (a_quantity)
+ end
+
+feature -- Access
+
+ name: READABLE_STRING_32
+ -- product name type of Coffee(Late, Cappuccino, Expresso)
+
+ option: READABLE_STRING_32
+ -- customization option Milk (skim, semi, whole)
+
+ size: READABLE_STRING_32
+ -- small, mediumm large
+
+ quantity: NATURAL_8
+
+feature -- Element Change
+
+ set_name (a_name: like name)
+ require
+ valid_name: is_valid_coffee_type (a_name)
+ do
+ name := a_name
+ ensure
+ name_assigned: name.same_string (a_name)
+ end
+
+ set_size (a_size: like size)
+ require
+ valid_size: is_valid_size_option (a_size)
+ do
+ size := a_size
+ ensure
+ size_assigned: size.same_string (a_size)
+ end
+
+ set_option (a_option: like option)
+ require
+ valid_option: is_valid_milk_type (a_option)
+ do
+ option := a_option
+ ensure
+ option_assigned: option.same_string (a_option)
+ end
+
+ set_quantity (a_quantity: NATURAL_8)
+ do
+ quantity := a_quantity
+ ensure
+ quantity_assigned: quantity = a_quantity
+ end
+
+feature -- Report
+
+ hash_code: INTEGER
+ -- Hash code value
+ do
+ Result := option.hash_code + name.hash_code + size.hash_code + quantity.hash_code
+ end
+
+invariant
+ valid_size: is_valid_size_option (size)
+ valid_coffe: is_valid_coffee_type (name)
+ valid_customization: is_valid_milk_type (option)
+ valid_quantity: quantity > 0
+
+note
+ copyright: "2011-2017, Javier Velilla and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+
+end
diff --git a/examples/rest/restbucks_CRUD/src/domain/order_item_validation.e b/examples/rest/restbucks_CRUD/src/domain/order_item_validation.e
new file mode 100644
index 00000000..c708c549
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/src/domain/order_item_validation.e
@@ -0,0 +1,51 @@
+note
+ description: "Summary description for {ORDER_ITEM_VALIDATION}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ ORDER_ITEM_VALIDATION
+
+feature -- Access
+
+ is_valid_coffee_type (a_type: READABLE_STRING_GENERAL): BOOLEAN
+ -- Is `a_type' a valid coffee type
+ do
+ Result := across coffe_types as ic some a_type.is_case_insensitive_equal (ic.item) end
+ end
+
+ Coffe_types: ARRAY [STRING]
+ -- List of valid Coffee types
+ once
+ Result := <<"late", "cappuccino", "expresso">>
+ end
+
+ is_valid_milk_type (a_type: READABLE_STRING_GENERAL): BOOLEAN
+ -- Is `a_type' a valid milk type
+ do
+ Result := across milk_types as ic some a_type.is_case_insensitive_equal (ic.item) end
+ end
+
+ Milk_types: ARRAY [STRING]
+ -- List of valid Milk types
+ once
+ Result := <<"skim", "semi", "whole">>
+ end
+
+ is_valid_size_option (a_option: READABLE_STRING_GENERAL): BOOLEAN
+ -- Is `a_option' a valid size option
+ do
+ Result := across size_options as ic some a_option.is_case_insensitive_equal (ic.item) end
+ end
+
+ Size_options: ARRAY [STRING]
+ -- List of valid Size_options
+ once
+ Result := <<"small", "medium", "large">>
+ end
+
+note
+ copyright: "2011-2017, Javier Velilla and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+
+end
diff --git a/examples/restbucksCRUD/src/policy_driven_resource/order_handler.e b/examples/rest/restbucks_CRUD/src/policy_driven_resource/order_handler.e
similarity index 78%
rename from examples/restbucksCRUD/src/policy_driven_resource/order_handler.e
rename to examples/rest/restbucks_CRUD/src/policy_driven_resource/order_handler.e
index 31410477..19e997c3 100644
--- a/examples/restbucksCRUD/src/policy_driven_resource/order_handler.e
+++ b/examples/rest/restbucks_CRUD/src/policy_driven_resource/order_handler.e
@@ -10,14 +10,10 @@ inherit
WSF_SKELETON_HANDLER
- SHARED_DATABASE_API
-
- SHARED_EJSON
+ SHARED_RESTBUCKS_API
REFACTORING_HELPER
- SHARED_ORDER_VALIDATION
-
WSF_RESOURCE_HANDLER_HELPER
rename
execute_options as helper_execute_options,
@@ -25,9 +21,17 @@ inherit
end
create
+ make
- make_with_router
+feature {NONE} -- Initialization
+ make (a_orderid_path_parameter_name: READABLE_STRING_GENERAL; a_router: WSF_ROUTER)
+ do
+ orderid_path_parameter_name := a_orderid_path_parameter_name
+ make_with_router (a_router)
+ end
+
+ orderid_path_parameter_name: READABLE_STRING_GENERAL
feature -- Execution variables
@@ -155,26 +159,21 @@ feature -- Access
-- If `a_strong' then the strong comparison function must be used.
local
l_id: STRING
- l_etag_util: ETAG_UTILS
do
l_id := order_id_from_request (req)
- if db_access.orders.has_key (l_id) then
- check attached db_access.orders.item (l_id) as l_order then
+ if l_id /= Void and then has_order (l_id) then
+ check attached order (l_id) as l_order then
-- postcondition of `has_key'
- create l_etag_util
- Result := a_etag.same_string (l_etag_util.md5_digest (l_order.out).as_string_32)
+ Result := a_etag.same_string (order_etag (l_order))
end
end
end
etag (req: WSF_REQUEST): detachable READABLE_STRING_8
-- Optional Etag for `req' in the requested variant
- local
- l_etag_utils: ETAG_UTILS
do
- create l_etag_utils
if attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then
- Result := l_etag_utils.md5_digest (l_order.out)
+ Result := order_etag (l_order)
end
end
@@ -232,10 +231,10 @@ feature -- Execution
else
-- the request is of the form /order/{orderid}
l_id := order_id_from_request (req)
- if db_access.orders.has_key (l_id) then
+ if l_id /= Void and then has_order (l_id) then
a_helper.set_resource_exists
if req.is_get_head_request_method then
- check attached db_access.orders.item (l_id) as l_order then
+ check attached order (l_id) as l_order then
-- postcondition `item_if_found' of `has_key'
req.set_execution_variable (Order_execution_variable, l_order)
end
@@ -259,7 +258,7 @@ feature -- GET/HEAD content
do
check attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then
-- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and
- if attached {JSON_VALUE} json.value (l_order) as jv then
+ if attached order_to_json (l_order) as jv then
req.set_execution_variable (Generated_content_execution_variable, jv.representation)
else
req.set_execution_variable (Generated_content_execution_variable, "")
@@ -300,13 +299,13 @@ feature -- DELETE
delete (req: WSF_REQUEST)
-- Delete resource named in `req' or set an error on `req.error_handler'.
- local
- l_id: STRING
do
- l_id := order_id_from_request (req)
- if db_access.orders.has_key (l_id) then
- if is_valid_to_delete (l_id) then
- delete_order (l_id)
+ if
+ attached order_id_from_request (req) as l_id and then
+ attached order (l_id) as l_order
+ then
+ if is_valid_to_delete (l_order) then
+ delete_order (l_order)
else
req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.method_not_allowed, "DELETE not valid",
"There is conflict while trying to delete the order, the order could not be deleted in the current state")
@@ -352,7 +351,7 @@ feature -- PUT/POST
-- Implementor must set error code of 200 OK or 204 No Content or 303 See Other or 500 Server Error.
do
if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then
- save_order (l_order)
+ submit_order (l_order)
compute_response_post (req, res, l_order)
else
handle_bad_request_response ("Not a valid order", req, res)
@@ -386,10 +385,10 @@ feature -- PUT/POST
l_id: STRING
do
if attached {READABLE_STRING_8} req.execution_variable (Request_entity_execution_variable) as l_request then
- l_order := extract_order_request (l_request)
+ l_order := order_from_request_input_data (l_request)
if req.is_put_request_method then
l_id := order_id_from_request (req)
- if l_order /= Void and then db_access.orders.has_key (l_id) then
+ if l_order /= Void and then l_id /= Void and then has_order (l_id) then
l_order.set_id (l_id)
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0)
req.set_execution_variable (Extracted_order_execution_variable, l_order)
@@ -422,153 +421,139 @@ feature -- PUT/POST
feature -- HTTP Methods
compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
- local
- h: HTTP_HEADER
- joc : JSON_ORDER_CONVERTER
- etag_utils : ETAG_UTILS
do
- create h.make
- create joc.make
- create etag_utils
- json.add_converter(joc)
-
- create h.make
- h.put_content_type_application_json
- if attached req.request_time as time then
- h.add_header ("Date:" +time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
- end
- h.add_header ("etag:" + etag_utils.md5_digest (l_order.out))
- if attached {JSON_VALUE} json.value (l_order) as jv then
- h.put_content_length (jv.representation.count)
+ res.header.put_content_type_application_json
+ res.header.put_utc_date (create {DATE_TIME}.make_now_utc)
+ res.header.add_header ("etag:" + order_etag (l_order))
+ if attached order_to_json (l_order) as jv then
+ res.header.put_content_length (jv.representation.count)
res.set_status_code ({HTTP_STATUS_CODE}.ok)
- res.put_header_text (h.string)
res.put_string (jv.representation)
end
end
compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
local
- h: HTTP_HEADER
l_msg : STRING
- l_location : STRING
- joc : JSON_ORDER_CONVERTER
do
- create h.make
-
- create joc.make
- json.add_converter(joc)
-
- h.put_content_type_application_json
- if attached {JSON_VALUE} json.value (l_order) as jv then
+ res.header.put_content_type_application_json
+ if attached order_to_json (l_order) as jv then
l_msg := jv.representation
- h.put_content_length (l_msg.count)
- if attached req.http_host as host then
- l_location := "http://" + host + req.request_uri + "/" + l_order.id
- h.put_location (l_location)
- end
- if attached req.request_time as time then
- h.put_utc_date (time)
- end
+ res.header.put_content_length (l_msg.count)
+ res.header.put_location (req.absolute_script_url (req.request_uri + "/" + l_order.id))
+ res.header.put_utc_date (create {DATE_TIME}.make_now_utc)
res.set_status_code ({HTTP_STATUS_CODE}.created)
- res.put_header_text (h.string)
res.put_string (l_msg)
end
end
feature {NONE} -- URI helper methods
- order_id_from_request (req: WSF_REQUEST): STRING
- -- Value of "orderid" template URI variable in `req'
- require
- req_attached: req /= Void
+ order_id_from_request (req: WSF_REQUEST): detachable STRING_8
do
- if attached {WSF_VALUE} req.path_parameter ("orderid") as l_value then
- Result := l_value.as_string.value.as_string_8
- else
- Result := ""
+ if attached {WSF_STRING} req.path_parameter (orderid_path_parameter_name) as p_id then
+ Result := p_id.url_encoded_value -- the ORDER id has to be valid STRING 8 value.
end
end
feature {NONE} -- Implementation Repository Layer
- retrieve_order ( id : STRING) : detachable ORDER
- -- get the order by id if it exist, in other case, Void
+ is_valid_to_delete (a_order: ORDER): BOOLEAN
+ -- Is the order identified by `a_id' in a state whre it can still be deleted?
do
- Result := db_access.orders.item (id)
- end
-
- save_order (an_order: ORDER)
- -- save the order to the repository
- local
- i : INTEGER
- do
- from
- i := 1
- until
- not db_access.orders.has_key ((db_access.orders.count + i).out)
- loop
- i := i + 1
- end
- an_order.set_id ((db_access.orders.count + i).out)
- an_order.set_status ("submitted")
- an_order.add_revision
- db_access.orders.force (an_order, an_order.id)
- end
-
-
- is_valid_to_delete ( an_id : STRING) : BOOLEAN
- -- Is the order identified by `an_id' in a state whre it can still be deleted?
- do
- if attached retrieve_order (an_id) as l_order then
- if order_validation.is_state_valid_to_update (l_order.status) then
+ if attached order (a_order.id) as l_order then
+ if api.is_state_valid_to_update (l_order.status) then
Result := True
end
end
end
- is_valid_to_update (an_order: ORDER) : BOOLEAN
- -- Check if there is a conflict while trying to update the order
+ is_valid_to_update (a_order: ORDER): BOOLEAN
+ -- Check if there is a conflict while trying to update the order.
do
- if attached retrieve_order (an_order.id) as l_order then
- if order_validation.is_state_valid_to_update (l_order.status) and then order_validation.is_valid_status_state (an_order.status) and then
- order_validation.is_valid_transition (l_order, an_order.status) then
- Result := True
- end
- end
- end
-
- update_order (an_order: ORDER)
- -- update the order to the repository
- do
- an_order.add_revision
- db_access.orders.force (an_order, an_order.id)
- end
-
- delete_order (an_order: STRING)
- -- update the order to the repository
- do
- db_access.orders.remove (an_order)
- end
-
- extract_order_request (l_post : STRING) : detachable ORDER
- -- extract an object Order from the request, or Void
- -- if the request is invalid
- local
- parser : JSON_PARSER
- joc : JSON_ORDER_CONVERTER
- do
- create joc.make
- json.add_converter(joc)
- create parser.make_with_string (l_post)
- parser.parse_content
- if parser.is_valid and then attached parser.parsed_json_value as jv then
- if attached {like extract_order_request} json.object (jv, "ORDER") as res then
- Result := res
+ if attached order (a_order.id) as l_existing_order then
+ if
+ api.is_state_valid_to_update (l_existing_order.status) and then
+ api.is_valid_status_state (a_order.status) and then
+ api.is_valid_transition (l_existing_order, a_order.status)
+ then
+ Result := True
end
end
end
+-- update_order (an_order: ORDER)
+-- -- update the order to the repository
+-- do
+-- an_order.add_revision
+-- db_access.orders.force (an_order, an_order.id)
+-- end
+
+-- delete_order (an_order: STRING)
+-- -- update the order to the repository
+-- do
+-- db_access.orders.remove (an_order)
+-- end
+
+feature -- Helpers
+
+ order_etag (a_order: ORDER): STRING_8
+ local
+ etag_utils: ETAG_UTILS
+ do
+ create etag_utils
+ Result := etag_utils.md5_digest (a_order.hash_code.out + a_order.revision.out)
+ end
+
+ order_from_request (req: WSF_REQUEST): detachable ORDER
+ -- extract an object Order from the request,
+ -- or Void if the request is invalid.
+ local
+ l_data: STRING
+ do
+ create l_data.make (req.content_length_value.to_integer_32)
+ req.read_input_data_into (l_data)
+
+ Result := order_from_request_input_data (l_data)
+ end
+
+ order_from_request_input_data (a_data: READABLE_STRING_8): detachable ORDER
+ -- extract an object Order from the request,
+ -- or Void if the request is invalid.
+ local
+ parser : JSON_PARSER
+ do
+ create parser.make_with_string (a_data)
+ parser.parse_content
+ if
+ parser.is_valid and then
+ attached parser.parsed_json_value as jv
+ then
+ Result := order_from_json (jv)
+ end
+ end
+
+feature {NONE} -- Conversion
+
+ order_to_json (obj: ORDER): JSON_VALUE
+ do
+ Result := order_serialization.to_json (obj)
+ end
+
+ order_from_json (jv: JSON_VALUE): detachable ORDER
+ do
+ if attached {ORDER} order_serialization.from_json (jv, {ORDER}) as o then
+ Result := o
+ end
+ end
+
+ order_serialization: JSON_SERIALIZATION
+ do
+ create Result
+ Result.register (create {ORDER_JSON_SERIALIZATION}, {ORDER})
+ end
+
note
- copyright: "2011-2015, Javier Velilla and others"
+ copyright: "2011-2017, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end
diff --git a/examples/rest/restbucks_CRUD/src/resource/order_handler.e b/examples/rest/restbucks_CRUD/src/resource/order_handler.e
new file mode 100644
index 00000000..e15a8c0c
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/src/resource/order_handler.e
@@ -0,0 +1,356 @@
+note
+ description: "{ORDER_HANDLER} handle the resources that we want to expose"
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ ORDER_HANDLER
+
+inherit
+ WSF_URI_TEMPLATE_HANDLER
+
+ WSF_RESOURCE_HANDLER_HELPER
+ redefine
+ do_get,
+ do_post,
+ do_put,
+ do_delete
+ end
+
+ SHARED_RESTBUCKS_API
+
+ REFACTORING_HELPER
+
+ WSF_SELF_DOCUMENTED_HANDLER
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_orderid_path_parameter_name: READABLE_STRING_GENERAL; a_router: WSF_ROUTER)
+ do
+ orderid_path_parameter_name := a_orderid_path_parameter_name
+ end
+
+ orderid_path_parameter_name: READABLE_STRING_GENERAL
+
+feature -- Execute
+
+ execute (req: WSF_REQUEST; res: WSF_RESPONSE)
+ -- Execute request handler
+ do
+ execute_methods (req, res)
+ end
+
+feature -- Request parameters
+
+ order_id_from_request (req: WSF_REQUEST): detachable STRING_8
+ do
+ if attached {WSF_STRING} req.path_parameter (orderid_path_parameter_name) as p_id then
+ Result := p_id.url_encoded_value -- the ORDER id has to be valid STRING 8 value.
+ end
+ end
+
+feature -- API DOC
+
+ api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, PUT, DELETE%N"
+
+
+feature -- Documentation
+
+ mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION
+ do
+ create Result.make (m)
+ if a_request_methods /= Void then
+ if a_request_methods.has_method_post then
+ Result.add_description ("URI:/order METHOD: POST")
+ elseif
+ a_request_methods.has_method_get
+ or a_request_methods.has_method_put
+ or a_request_methods.has_method_delete
+ then
+ Result.add_description ("URI:/order/{orderid} METHOD: GET, PUT, DELETE")
+ end
+ end
+ end
+
+feature -- HTTP Methods
+
+ do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
+ --
+ do
+ if
+ attached order_id_from_request (req) as l_id and then
+ attached order (l_id) as l_order
+ then
+ if is_conditional_get (req, l_order) then
+ handle_resource_not_modified_response ("The resource" + req.percent_encoded_path_info + " does not change", req, res)
+ else
+ compute_response_get (req, res, l_order)
+ end
+ else
+ handle_resource_not_found_response ("The following resource" + req.percent_encoded_path_info + " is not found ", req, res)
+ end
+ end
+
+ is_conditional_get (req : WSF_REQUEST; l_order : ORDER) : BOOLEAN
+ -- Check if If-None-Match is present and then if there is a representation that has that etag
+ -- if the representation hasn't changed, we return TRUE
+ -- then the response is a 304 with no entity body returned.
+ local
+ etag_util : ETAG_UTILS
+ do
+ if attached req.meta_string_variable ("HTTP_IF_NONE_MATCH") as if_none_match then
+ create etag_util
+ if if_none_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then
+ Result := True
+ end
+ end
+ end
+
+ compute_response_get (req: WSF_REQUEST; res: WSF_RESPONSE; l_order: ORDER)
+ local
+ l_msg : STRING
+ do
+ res.header.put_content_type_application_json
+ if attached order_to_json (l_order) as jv then
+ l_msg := jv.representation
+ res.header.put_content_length (l_msg.count)
+ res.header.put_utc_date (create {DATE_TIME}.make_now_utc)
+ res.header.add_header ("etag:" + order_etag (l_order))
+ res.set_status_code ({HTTP_STATUS_CODE}.ok)
+ res.put_string (l_msg)
+ end
+ end
+
+ do_put (req: WSF_REQUEST; res: WSF_RESPONSE)
+ -- Updating a resource with PUT
+ -- A successful PUT request will not create a new resource, instead it will
+ -- change the state of the resource identified by the current uri.
+ -- If success we response with 200 and the updated order.
+ -- 404 if the order is not found
+ -- 400 in case of a bad request
+ -- 500 internal server error
+ -- If the request is a Conditional PUT, and it does not mat we response
+ -- 415, precondition failed.
+ do
+ if attached order_id_from_request (req) as l_id then
+ if has_order (l_id) then
+ if attached order_from_request (req) as l_order then
+ if l_order.has_id and then not l_id.same_string (l_order.id) then
+ -- If request input data define an order id different from the one in request path!
+ handle_resource_conflict_response ("ERROR: conflict between the url defining order id [" + l_id + "] and request data of order with id [" + l_order.id + "]!", req, res)
+ else
+ l_order.set_id (l_id)
+ if is_valid_to_update (l_order) then
+ if is_conditional_put (req, l_order) then
+ update_order (l_order)
+ compute_response_put (req, res, l_order)
+ else
+ handle_precondition_fail_response ("ERROR", req, res)
+ end
+ else
+ --| TODO: build message explaining the conflict to the client!
+ --| FIXME: Here we need to define the Allow methods
+ handle_resource_conflict_response ("ERROR: order could not be updated!", req, res)
+ end
+ end
+ else
+ handle_bad_request_response ("ERROR: invalid request input data!", req, res)
+ end
+ else
+ handle_bad_request_response ("ERROR: Order [" + l_id.out + "] was not found!", req, res)
+ end
+ else
+ handle_bad_request_response ("ERROR: Missing ORDER id information!", req, res)
+ end
+ end
+
+ is_conditional_put (req: WSF_REQUEST; a_order: ORDER): BOOLEAN
+ -- Check if If-Match is present and then if `a_order` is matching that etag,
+ -- if etag from `a_order` is unchanged, return True.
+ do
+ if attached req.meta_string_variable ("HTTP_IF_MATCH") as if_match then
+ if attached order (a_order.id) as l_order then
+ if if_match.same_string (order_etag (l_order)) then
+ Result := True
+ end
+ else
+ Result := False
+ end
+ else
+ Result := True
+ end
+ end
+
+ compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
+ do
+ res.header.put_content_type_application_json
+ res.header.put_utc_date (create {DATE_TIME}.make_now_utc)
+ res.header.add_header ("etag:" + order_etag (l_order))
+ if attached order_to_json (l_order) as jv then
+ res.header.put_content_length (jv.representation.count)
+ res.set_status_code ({HTTP_STATUS_CODE}.ok)
+ res.put_string (jv.representation)
+ end
+ end
+
+ do_delete (req: WSF_REQUEST; res: WSF_RESPONSE)
+ -- Here we use DELETE to cancel an order, if that order is in state where
+ -- it can still be canceled.
+ -- 200 if is ok
+ -- 404 Resource not found
+ -- 405 if consumer and service's view of the resouce state is inconsisent
+ -- 500 if we have an internal server error
+ do
+ if
+ attached order_id_from_request (req) as l_id and then
+ attached order (l_id) as l_order
+ then
+ if is_valid_to_delete (l_order) then
+ delete_order (l_order)
+ compute_response_delete (req, res)
+ else
+ --| FIXME: Here we need to define the Allow methods
+ handle_method_not_allowed_response (req.percent_encoded_path_info + "%N Conflict while trying to delete the order! The order could not be deleted in the current state!", req, res)
+ end
+ else
+ handle_resource_not_found_response ("Order not found", req, res)
+ end
+ end
+
+ compute_response_delete (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ res.header.put_content_type_application_json
+ res.header.put_utc_date (create {DATE_TIME}.make_now_utc)
+ res.set_status_code ({HTTP_STATUS_CODE}.no_content)
+ end
+
+ do_post (req: WSF_REQUEST; res: WSF_RESPONSE)
+ -- Here the convention is the following.
+ -- POST is used for creation and the server determines the URI
+ -- of the created resource.
+ -- If the request post is SUCCESS, the server will create the order and will response with
+ -- HTTP_RESPONSE 201 CREATED, the Location header will contains the newly created order's URI
+ -- if the request post is not SUCCESS, the server will response with
+ -- HTTP_RESPONSE 400 BAD REQUEST, the client send a bad request
+ -- HTTP_RESPONSE 500 INTERNAL_SERVER_ERROR, when the server can deliver the request
+ do
+ if attached order_from_request (req) as l_order then
+ submit_order (l_order)
+ compute_response_post (req, res, l_order)
+ else
+ handle_bad_request_response ("ERROR: invalid request input data!", req, res)
+ end
+ end
+
+ compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
+ local
+ l_msg : STRING
+ do
+ res.header.put_content_type_application_json
+ if attached order_to_json (l_order) as jv then
+ l_msg := jv.representation
+ res.header.put_content_length (l_msg.count)
+ res.header.put_location (req.absolute_script_url (req.request_uri + "/" + l_order.id))
+ res.header.put_utc_date (create {DATE_TIME}.make_now_utc)
+ res.set_status_code ({HTTP_STATUS_CODE}.created)
+ res.put_string (l_msg)
+ end
+ end
+
+feature {NONE} -- URI helper methods
+
+ get_order_id_from_path (a_path: READABLE_STRING_32): READABLE_STRING_32
+ do
+ Result := a_path.split ('/').at (3)
+ end
+
+feature {NONE} -- Implementation Repository Layer
+
+ order_etag (a_order: ORDER): STRING_8
+ local
+ etag_utils: ETAG_UTILS
+ do
+ create etag_utils
+ Result := etag_utils.md5_digest (a_order.hash_code.out + a_order.revision.out)
+ end
+
+ is_valid_to_delete (a_order: ORDER): BOOLEAN
+ -- Is the order identified by `a_id' in a state whre it can still be deleted?
+ do
+ if attached order (a_order.id) as l_order then
+ if api.is_state_valid_to_update (l_order.status) then
+ Result := True
+ end
+ end
+ end
+
+ is_valid_to_update (a_order: ORDER): BOOLEAN
+ -- Check if there is a conflict while trying to update the order.
+ do
+ if attached order (a_order.id) as l_existing_order then
+ if
+ api.is_state_valid_to_update (l_existing_order.status) and then
+ api.is_valid_status_state (a_order.status) and then
+ api.is_valid_transition (l_existing_order, a_order.status)
+ then
+ Result := True
+ end
+ end
+ end
+
+ order_from_request (req: WSF_REQUEST): detachable ORDER
+ -- extract an object Order from the request,
+ -- or Void if the request is invalid.
+ local
+ l_data: STRING
+ do
+ create l_data.make (req.content_length_value.to_integer_32)
+ req.read_input_data_into (l_data)
+
+ Result := order_from_request_input_data (l_data)
+ end
+
+ order_from_request_input_data (a_data: READABLE_STRING_8): detachable ORDER
+ -- extract an object Order from the request,
+ -- or Void if the request is invalid.
+ local
+ parser : JSON_PARSER
+ do
+ create parser.make_with_string (a_data)
+ parser.parse_content
+ if
+ parser.is_valid and then
+ attached parser.parsed_json_value as jv
+ then
+ Result := order_from_json (jv)
+ end
+ end
+
+feature {NONE} -- Conversion
+
+ order_to_json (obj: ORDER): JSON_VALUE
+ do
+ Result := order_serialization.to_json (obj)
+ end
+
+ order_from_json (jv: JSON_VALUE): detachable ORDER
+ do
+ if attached {ORDER} order_serialization.from_json (jv, {ORDER}) as o then
+ Result := o
+ end
+ end
+
+ order_serialization: JSON_SERIALIZATION
+ do
+ create Result
+ Result.register (create {ORDER_JSON_SERIALIZATION}, {ORDER})
+ end
+
+note
+ copyright: "2011-2017, Javier Velilla and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+end
diff --git a/examples/restbucksCRUD/src/restbucks_server.e b/examples/rest/restbucks_CRUD/src/restbucks_server.e
similarity index 100%
rename from examples/restbucksCRUD/src/restbucks_server.e
rename to examples/rest/restbucks_CRUD/src/restbucks_server.e
diff --git a/examples/rest/restbucks_CRUD/src/restbucks_server_execution.e b/examples/rest/restbucks_CRUD/src/restbucks_server_execution.e
new file mode 100644
index 00000000..3dadb852
--- /dev/null
+++ b/examples/rest/restbucks_CRUD/src/restbucks_server_execution.e
@@ -0,0 +1,90 @@
+note
+ description : "REST Buck server"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class RESTBUCKS_SERVER_EXECUTION
+
+inherit
+ WSF_ROUTED_SKELETON_EXECUTION
+ undefine
+ requires_proxy
+ end
+
+ WSF_NO_PROXY_POLICY
+
+ SHARED_RESTBUCKS_API
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ setup_router
+ local
+ doc: WSF_ROUTER_SELF_DOCUMENTATION_HANDLER
+ do
+ setup_order_handler (router)
+
+ create doc.make_hidden (router)
+ router.handle ("/api/doc", doc, router.methods_GET)
+
+ -- Those 2 following routes are not for the REST api, but mainly to make simpler to test this example.
+ router.handle ("/", create {WSF_URI_AGENT_HANDLER}.make (agent handle_home), router.methods_GET)
+ router.handle ("/new_order", create {WSF_URI_AGENT_HANDLER}.make (agent handle_new_order), router.methods_GET)
+ end
+
+ setup_order_handler (a_router: WSF_ROUTER)
+ local
+ order_handler: ORDER_HANDLER
+ do
+ create order_handler.make ("orderid", a_router)
+ router.handle ("/order", order_handler, router.methods_POST)
+ router.handle ("/order/{orderid}", order_handler, router.methods_GET + router.methods_DELETE + router.methods_PUT)
+ end
+
+feature -- Handler
+
+ handle_home (req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ l_page: WSF_HTML_PAGE_RESPONSE
+ l_html: STRING
+ do
+ create l_page.make
+ create l_html.make_empty
+ l_html.append ("
RESTbucks example (a Create-Read-Update-Delete REST API)
%N")
+ l_html.append ("
")
+ l_html.append ("
To test this example: create new ORDER entry , and get its json representation right away.
")
+ l_page.set_body (l_html)
+ l_page.set_title ("RESTbucks example")
+ res.send (l_page)
+ end
+
+ handle_new_order (req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ o: ORDER
+ redir: WSF_REDIRECTION_RESPONSE
+ do
+ create o.make_empty
+ o.set_location ("TakeAway")
+ o.add_item (create {ORDER_ITEM}.make ("late", "large", "whole", 1))
+ o.add_item (create {ORDER_ITEM}.make ("expresso", "small", "skim", 2))
+ submit_order (o)
+ create redir.make (req.absolute_script_url ("/order/" + o.id))
+ redir.set_content ("Order " + o.id + " created.", Void)
+ res.send (redir)
+ end
+
+note
+ copyright: "2011-2017, Javier Velilla 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
diff --git a/examples/restbucksCRUD/src/utils/etag_utils.e b/examples/rest/restbucks_CRUD/src/utils/etag_utils.e
similarity index 100%
rename from examples/restbucksCRUD/src/utils/etag_utils.e
rename to examples/rest/restbucks_CRUD/src/utils/etag_utils.e
diff --git a/examples/restbucksCRUD/client/client-safe.ecf b/examples/restbucksCRUD/client/client-safe.ecf
deleted file mode 100644
index 11ce67ad..00000000
--- a/examples/restbucksCRUD/client/client-safe.ecf
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
- /.git$
- /.svn$
- /EIFGENs$
-
-
-
-
-
-
-
-
-
-
diff --git a/examples/restbucksCRUD/client/src/restbuck_client.e b/examples/restbucksCRUD/client/src/restbuck_client.e
deleted file mode 100644
index 2227690f..00000000
--- a/examples/restbucksCRUD/client/src/restbuck_client.e
+++ /dev/null
@@ -1,154 +0,0 @@
-note
- description : "Objects that ..."
- author : "$Author$"
- date : "$Date$"
- revision : "$Revision$"
-
-class
- RESTBUCK_CLIENT
-
-create
- make
-
-feature {NONE} -- Initialization
-
- make
- -- Initialize `Current'.
- local
- h: DEFAULT_HTTP_CLIENT
- sess: HTTP_CLIENT_SESSION
- resp : detachable HTTP_CLIENT_RESPONSE
- l_location : detachable READABLE_STRING_8
- body : STRING
- do
- create h
- sess := h.new_session ("http://127.0.0.1:9090")
--- Uncomment the following 2 lines, if you use fiddler2 web debugging tool
--- sess.set_is_debug (True)
--- sess.set_proxy ("127.0.0.1", 8888)
-
- -- Create Order
- print ("%N Create Order %N")
- resp := create_order (sess)
-
-
- -- Read the Order
- print ("%N Read Order %N")
- l_location := resp.header ("Location")
- resp := read_order (sess, l_location)
-
-
- -- Update the Order
- if resp /= Void and then attached resp.body as l_body then
- body := l_body.as_string_8
- body.replace_substring_all ("takeAway", "in Shop")
- print ("%N Update Order %N")
- resp := update_order (sess, l_location, body)
- end
- end
-
- update_order ( sess: HTTP_CLIENT_SESSION; uri : detachable READABLE_STRING_8; a_body : STRING): detachable HTTP_CLIENT_RESPONSE
- local
- context : HTTP_CLIENT_REQUEST_CONTEXT
- do
- if attached uri as l_uri then
- sess.set_base_url (l_uri)
- create context.make
- context.headers.put ("application/json", "Content-Type")
- Result := sess.put ("", context, a_body )
- -- Show headers
- across
- Result.headers as l_headers
- loop
- print (l_headers.item.name)
- print (":")
- print (l_headers.item.value)
- io.put_new_line
- end
-
- -- Show body
- print (Result.body)
- io.put_new_line
- end
- end
-
-
- read_order ( sess: HTTP_CLIENT_SESSION; uri : detachable READABLE_STRING_8): detachable HTTP_CLIENT_RESPONSE
- do
- if attached uri as l_uri then
- sess.set_base_url (l_uri)
- Result := sess.get ("", Void)
- -- Show headers
- across
- Result.headers as l_headers
- loop
- print (l_headers.item.name)
- print (":")
- print (l_headers.item.value)
- io.put_new_line
- end
-
- -- Show body
- print (Result.body)
- io.put_new_line
- end
- end
-
-
-
- create_order (sess: HTTP_CLIENT_SESSION) : HTTP_CLIENT_RESPONSE
- local
- s: READABLE_STRING_8
- j: JSON_PARSER
- id: detachable STRING
- context : HTTP_CLIENT_REQUEST_CONTEXT
- do
- s := "[
- {
- "location":"takeAway",
- "items":[
- {
- "name":"Late",
- "option":"skim",
- "size":"Small",
- "quantity":1
- }
- ]
- }
- ]"
-
- create context.make
- context.headers.put ("application/json", "Content-Type")
- Result := sess.post ("/order", context, s)
- -- Show the Headers
- across
- Result.headers as l_headers
- loop
- print (l_headers.item.name)
- print (":")
- print (l_headers.item.value)
- io.put_new_line
- end
-
-
- -- Show the Response body
- if attached Result.body as m then
- create j.make_with_string (m)
- j.parse_content
- if j.is_valid and then attached j.parsed_json_object as j_o then
- if attached {JSON_STRING} j_o.item ("id") as l_id then
- id := l_id.item
- end
- print (m)
- io.put_new_line
- end
- end
- end
-
-
-feature {NONE} -- Implementation
-
-invariant
--- invariant_clause: True
-
-end
diff --git a/examples/restbucksCRUD/readme.txt b/examples/restbucksCRUD/readme.txt
deleted file mode 100644
index e69de29b..00000000
diff --git a/examples/restbucksCRUD/src/database/database_api.e b/examples/restbucksCRUD/src/database/database_api.e
deleted file mode 100644
index efc0bf20..00000000
--- a/examples/restbucksCRUD/src/database/database_api.e
+++ /dev/null
@@ -1,26 +0,0 @@
-note
- description: "Summary description for {DATABASE_API}."
- author: ""
- date: "$Date$"
- revision: "$Revision$"
-
-class
- DATABASE_API
-create
- make
-
-feature -- Initialization
-
- make
- do
- create orders.make (10)
- end
-
-feature -- Access
-
- orders: HASH_TABLE [ORDER, STRING]
-
-;note
- copyright: "2011-2012, Javier Velilla and others"
- license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
-end
diff --git a/examples/restbucksCRUD/src/database/shared_database_api.e b/examples/restbucksCRUD/src/database/shared_database_api.e
deleted file mode 100644
index de4c9a22..00000000
--- a/examples/restbucksCRUD/src/database/shared_database_api.e
+++ /dev/null
@@ -1,19 +0,0 @@
-note
- description: "Summary description for {SHARED_DATABASE_API}."
- author: ""
- date: "$Date$"
- revision: "$Revision$"
-
-class
- SHARED_DATABASE_API
-
-feature -- Access
-
- db_access: DATABASE_API
- once
- create Result.make
- end
-note
- copyright: "2011-2012, Javier Velilla and others"
- license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
-end
diff --git a/examples/restbucksCRUD/src/domain/item.e b/examples/restbucksCRUD/src/domain/item.e
deleted file mode 100644
index 2e27cf5a..00000000
--- a/examples/restbucksCRUD/src/domain/item.e
+++ /dev/null
@@ -1,90 +0,0 @@
-note
- description: "Summary description for {ITEM}."
- author: ""
- date: "$Date$"
- revision: "$Revision$"
-
-class
- ITEM
-inherit
- ITEM_CONSTANTS
-create
- make
-feature -- Initialization
- make ( a_name : STRING_32 ; a_size:STRING_32; a_option: STRING_32; a_quantity:INTEGER_8)
- do
- set_name (a_name)
- set_size (a_size)
- set_option (a_option)
- set_quantity (a_quantity)
- end
-
-feature -- Access
- name : STRING
- -- product name type of Coffee(Late, Cappuccino, Expresso)
-
- option : STRING
- -- customization option Milk (skim, semi, whole)
-
- size : STRING
- -- small, mediumm large
-
- quantity :INTEGER
-
-
-
-
-feature -- Element Change
- set_name (a_name: STRING)
- require
- valid_name: is_valid_coffee_type (a_name)
- do
- name := a_name
- ensure
- name_assigned : name.same_string(a_name)
- end
-
- set_size (a_size: STRING)
- require
- valid_size : is_valid_size_option (a_size)
- do
- size := a_size
- ensure
- size_assigned : size.same_string(a_size)
- end
-
- set_option (an_option: STRING)
- require
- valid_option : is_valid_milk_type (an_option)
- do
- option := an_option
- ensure
- option_assigned : option.same_string (an_option)
- end
-
- set_quantity (a_quantity: INTEGER)
- require
- valid_quantity : a_quantity > 0
- do
- quantity := a_quantity
- ensure
- quantity_assigned : quantity = a_quantity
- end
-
-feature -- Report
- hash_code: INTEGER
- --Hash code value
- do
- Result := option.hash_code + name.hash_code + size.hash_code + quantity.hash_code
- end
-
-
-invariant
- valid_size : is_valid_size_option (size)
- valid_coffe : is_valid_coffee_type (name)
- valid_customization : is_valid_milk_type (option)
- valid_quantity : quantity > 0
-note
- copyright: "2011-2012, Javier Velilla and others"
- license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
-end
diff --git a/examples/restbucksCRUD/src/domain/item_constants.e b/examples/restbucksCRUD/src/domain/item_constants.e
deleted file mode 100644
index ecea445d..00000000
--- a/examples/restbucksCRUD/src/domain/item_constants.e
+++ /dev/null
@@ -1,54 +0,0 @@
-note
- description: "Summary description for {ITEM_CONSTANTS}."
- author: ""
- date: "$Date$"
- revision: "$Revision$"
-
-class
- ITEM_CONSTANTS
-feature -- Access
- is_valid_coffee_type (a_type: STRING) : BOOLEAN
- --is `a_type' a valid coffee type
- do
- a_type.to_lower
- coffe_types.compare_objects
- Result := coffe_types.has (a_type)
- end
-
- Coffe_types : ARRAY[STRING]
- -- List of valid Coffee types
- once
- Result := <<"late","cappuccino", "expresso">>
- end
-
- is_valid_milk_type (a_type: STRING) : BOOLEAN
- --is `a_type' a valid milk type
- do
- a_type.to_lower
- milk_types.compare_objects
- Result := milk_types.has (a_type)
- end
-
- Milk_types : ARRAY[STRING]
- -- List of valid Milk types
- once
- Result := <<"skim","semi", "whole">>
- end
-
- is_valid_size_option (an_option: STRING) : BOOLEAN
- --is `an_option' a valid size option
- do
- an_option.to_lower
- size_options.compare_objects
- Result := size_options.has (an_option)
- end
-
- Size_options : ARRAY[STRING]
- -- List of valid Size_options
- once
- Result := <<"small","mediumn", "large">>
- end
-note
- copyright: "2011-2012, Javier Velilla and others"
- license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
-end
diff --git a/examples/restbucksCRUD/src/domain/json_order_converter.e b/examples/restbucksCRUD/src/domain/json_order_converter.e
deleted file mode 100644
index 1d022062..00000000
--- a/examples/restbucksCRUD/src/domain/json_order_converter.e
+++ /dev/null
@@ -1,176 +0,0 @@
-note
- description: "Summary description for {JSON_ORDER_CONVERTER}."
- author: ""
- date: "$Date$"
- revision: "$Revision$"
-
-class
- JSON_ORDER_CONVERTER
-inherit
- JSON_CONVERTER
-create
- make
-feature -- Initialization
- make
- do
- create object.make ("","","")
- end
-feature -- Access
- object : ORDER
-
-
- value : detachable JSON_OBJECT
-feature -- Conversion
-
- from_json (j: attached like value): detachable like object
- -- Convert from JSON value. Returns Void if unable to convert
- local
- s_id, s_location, s_status: detachable STRING_32
- q: INTEGER_8
- o: ORDER
- i : ITEM
- l_array : detachable ARRAYED_LIST [JSON_VALUE]
- is_valid_from_json : BOOLEAN
- do
- is_valid_from_json := True
-
- if attached {STRING_32} json.object (j.item (id_key), Void) as l_id then
- s_id := s_id
- end
- if attached {STRING_32} json.object (j.item (location_key), Void) as l_location then
- s_location := l_location
- end
- if attached {STRING_32} json.object (j.item (status_key), Void) as l_status then
- s_status := l_status
- end
-
- create o.make ("", s_location, s_status)
-
- if attached {JSON_ARRAY} j.item (items_key) as l_val then
- l_array := l_val.array_representation
- from
- l_array.start
- until
- l_array.after
- loop
- if attached {JSON_OBJECT} l_array.item_for_iteration as jv then
- if attached {INTEGER_8} json.object (jv.item (quantity_key), Void) as l_integer then
- q := l_integer
- else
- q := 0
- end
- if
- attached {STRING_32} json.object (jv.item (name_key), Void) as s_name and then
- attached {STRING_32} json.object (jv.item (size_key), Void) as s_key and then
- attached {STRING_32} json.object (jv.item (option_key), Void) as s_option
- then
- if is_valid_item_customization (s_name, s_key, s_option,q) then
- create i.make (s_name, s_key, s_option, q)
- o.add_item (i)
- else
- is_valid_from_json := False
- end
- else
- is_valid_from_json := False
- end
- end
-
- l_array.forth
- end
- end
- if not is_valid_from_json or o.items.is_empty then
- Result := Void
- else
- Result := o
- end
- end
-
- to_json (o: like object): like value
- -- Convert to JSON value
- local
- ja : JSON_ARRAY
- i : ITEM
- jv: JSON_OBJECT
- do
- create Result.make
--- Result.put (json.value (o.id), id_key)
- Result.put (json.value (o.location), location_key)
- Result.put (json.value (o.status), status_key)
- from
- create ja.make_empty
- o.items.start
- until
- o.items.after
- loop
- i := o.items.item_for_iteration
- create jv.make
- jv.put (json.value (i.name), name_key)
- jv.put (json.value (i.size),size_key)
- jv.put (json.value (i.quantity), quantity_key)
- jv.put (json.value (i.option), option_key)
- ja.add (jv)
- o.items.forth
- end
- Result.put (ja, items_key)
- end
-
- feature {NONE} -- Implementation
- id_key: JSON_STRING
- once
- create Result.make_from_string ("id")
- end
-
- location_key: JSON_STRING
- once
- create Result.make_from_string ("location")
- end
-
- status_key: JSON_STRING
- once
- create Result.make_from_string ("status")
- end
-
- items_key : JSON_STRING
- once
- create Result.make_from_string ("items")
- end
-
-
- name_key : JSON_STRING
-
- once
- create Result.make_from_string ("name")
- end
-
- size_key : JSON_STRING
-
- once
- create Result.make_from_string ("size")
- end
-
- quantity_key : JSON_STRING
-
- once
- create Result.make_from_string ("quantity")
- end
-
-
- option_key : JSON_STRING
-
- once
- create Result.make_from_string ("option")
- end
-feature -- Validation
-
- is_valid_item_customization ( name : STRING_32; size: STRING_32; option : STRING_32; quantity : INTEGER_8 ) : BOOLEAN
- local
- ic : ITEM_CONSTANTS
- do
- create ic
- Result := ic.is_valid_coffee_type (name) and ic.is_valid_milk_type (option) and ic.is_valid_size_option (size) and quantity > 0
- end
-
-note
- copyright: "2011-2015, Javier Velilla and others"
- license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
-end
diff --git a/examples/restbucksCRUD/src/domain/order.e b/examples/restbucksCRUD/src/domain/order.e
deleted file mode 100644
index 721d2256..00000000
--- a/examples/restbucksCRUD/src/domain/order.e
+++ /dev/null
@@ -1,114 +0,0 @@
-note
- description: "Summary description for {ORDER}."
- author: ""
- date: "$Date$"
- revision: "$Revision$"
-
-class
- ORDER
-create
- make
-feature -- Initialization
-
- make ( an_id : detachable STRING_32; a_location: detachable STRING_32; a_status: detachable STRING_32)
- do
- create {ARRAYED_LIST [ITEM]} items.make (10)
- if an_id /= Void then
- set_id (an_id)
- else
- set_id ("")
- end
- if a_location /= Void then
- set_location (a_location)
- else
- set_location ("")
- end
- if a_status /= Void then
- set_status (a_status)
- else
- set_status ("")
- end
- revision := 0
- end
-
-feature -- Access
-
- id : STRING_32
- location : STRING_32
- items: LIST[ITEM]
- status : STRING_32
- revision : INTEGER
-
-feature -- element change
-
- set_id (an_id : STRING_32)
- do
- id := an_id
- ensure
- id_assigned : id.same_string (an_id)
- end
-
- set_location (a_location : STRING_32)
- do
- location := a_location
- ensure
- location_assigned : location.same_string (a_location)
- end
-
- set_status (a_status : STRING_32)
- do
- status := a_status
- ensure
- status_asigned : status.same_string (a_status)
- end
-
- add_item (a_item : ITEM)
- require
- valid_item: a_item /= Void
- do
- items.force (a_item)
- ensure
- has_item : items.has (a_item)
- end
-
- add_revision
- do
- revision := revision + 1
- ensure
- revision_incremented : old revision + 1 = revision
- end
-
-feature -- Etag
-
- etag : STRING_32
- -- Etag generation for Order objects
- do
- Result := hash_code.out + revision.out
- end
-
-
-feature -- Output
-
-feature -- Report
-
- hash_code: INTEGER_32
- -- Hash code value
- do
- from
- items.start
- Result := items.item.hash_code
- until
- items.off
- loop
- Result:= ((Result \\ 8388593) |<< 8) + items.item.hash_code
- items.forth
- end
- if items.count > 1 then
- Result := Result \\ items.count
- end
- end
-
-note
- copyright: "2011-2012, Javier Velilla and others"
- license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
-end
diff --git a/examples/restbucksCRUD/src/domain/order_validation.e b/examples/restbucksCRUD/src/domain/order_validation.e
deleted file mode 100644
index 5583a318..00000000
--- a/examples/restbucksCRUD/src/domain/order_validation.e
+++ /dev/null
@@ -1,56 +0,0 @@
-note
- description: "Summary description for {ORDER_TRANSITIONS}."
- author: ""
- date: "$Date$"
- revision: "$Revision$"
-
-class
- ORDER_VALIDATION
-feature -- Access
-
- is_valid_status_state (a_status: STRING) : BOOLEAN
- --is `a_status' a valid coffee order state
- do
- a_status.to_lower
- Order_states.compare_objects
- Result := Order_states.has (a_status)
- end
-
- Order_states : ARRAY[STRING]
- -- List of valid status states
- once
- Result := <<"submitted","pay","payed", "cancel","canceled","prepare","prepared","deliver","completed">>
- end
-
-
- is_valid_transition (order:ORDER a_status : STRING) :BOOLEAN
- -- Given the current order state, determine if the transition is valid
- do
- a_status.to_lower
- if order.status.same_string ("submitted") then
- Result := a_status.same_string ("pay") or a_status.same_string ("cancel") or order.status.same_string (a_status)
- elseif order.status.same_string ("pay") then
- Result := a_status.same_string ("payed") or order.status.same_string (a_status)
- elseif order.status.same_string ("cancel") then
- Result := a_status.same_string ("canceled") or order.status.same_string (a_status)
- elseif order.status.same_string ("payed") then
- Result := a_status.same_string ("prepared") or order.status.same_string (a_status)
- elseif order.status.same_string ("prepared") then
- Result := a_status.same_string ("deliver") or order.status.same_string (a_status)
- elseif order.status.same_string ("deliver") then
- Result := a_status.same_string ("completed") or order.status.same_string (a_status)
- end
- end
-
- is_state_valid_to_update ( a_status : STRING) : BOOLEAN
- -- Given the current state `a_status' of an order, is possible to update the order?
- do
- if a_status.same_string ("submitted") or else a_status.same_string ("pay") or else a_status.same_string ("payed") then
- Result := true
- end
- end
-
-note
- copyright: "2011-2012, Javier Velilla and others"
- license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
-end
diff --git a/examples/restbucksCRUD/src/domain/shared_order_validation.e b/examples/restbucksCRUD/src/domain/shared_order_validation.e
deleted file mode 100644
index 4e3a1d33..00000000
--- a/examples/restbucksCRUD/src/domain/shared_order_validation.e
+++ /dev/null
@@ -1,19 +0,0 @@
-note
- description: "Summary description for {SHARED_ORDER_VALIDATION}."
- author: ""
- date: "$Date$"
- revision: "$Revision$"
-
-class
- SHARED_ORDER_VALIDATION
-
-feature
- order_validation : ORDER_VALIDATION
- once
- create Result
- end
-
-note
- copyright: "2011-2012, Javier Velilla and others"
- license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
-end
diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e
deleted file mode 100644
index 70de9fb8..00000000
--- a/examples/restbucksCRUD/src/resource/order_handler.e
+++ /dev/null
@@ -1,401 +0,0 @@
-note
- description: "{ORDER_HANDLER} handle the resources that we want to expose"
- author: ""
- date: "$Date$"
- revision: "$Revision$"
-
-class ORDER_HANDLER
-inherit
-
- WSF_URI_TEMPLATE_HANDLER
-
- WSF_RESOURCE_HANDLER_HELPER
- redefine
- do_get,
- do_post,
- do_put,
- do_delete
- end
-
- SHARED_DATABASE_API
-
- SHARED_EJSON
-
- REFACTORING_HELPER
-
- SHARED_ORDER_VALIDATION
-
- WSF_SELF_DOCUMENTED_HANDLER
-
-create
- make_with_router
-
-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
- -- Associated router that could be used for advanced strategy
-
-feature -- Execute
-
- execute (req: WSF_REQUEST; res: WSF_RESPONSE)
- -- Execute request handler
- do
- execute_methods (req, res)
- end
-
-feature -- API DOC
-
- api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, PUT, DELETE%N"
-
-
-feature -- Documentation
-
- mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION
- do
- create Result.make (m)
- if a_request_methods /= Void then
- if a_request_methods.has_method_post then
- Result.add_description ("URI:/order METHOD: POST")
- elseif
- a_request_methods.has_method_get
- or a_request_methods.has_method_put
- or a_request_methods.has_method_delete
- then
- Result.add_description ("URI:/order/{orderid} METHOD: GET, PUT, DELETE")
- end
- end
- end
-
-feature -- HTTP Methods
-
- do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
- --
- local
- id: STRING
- do
- if attached req.path_info as l_path_info then
- id := get_order_id_from_path (l_path_info)
- if attached retrieve_order (id) as l_order then
- if is_conditional_get (req, l_order) then
- handle_resource_not_modified_response ("The resource" + l_path_info + "does not change", req, res)
- else
- compute_response_get (req, res, l_order)
- end
- else
- handle_resource_not_found_response ("The following resource" + l_path_info + " is not found ", req, res)
- end
- end
- end
-
- is_conditional_get (req : WSF_REQUEST; l_order : ORDER) : BOOLEAN
- -- Check if If-None-Match is present and then if there is a representation that has that etag
- -- if the representation hasn't changed, we return TRUE
- -- then the response is a 304 with no entity body returned.
- local
- etag_util : ETAG_UTILS
- do
- if attached req.meta_string_variable ("HTTP_IF_NONE_MATCH") as if_none_match then
- create etag_util
- if if_none_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then
- Result := True
- end
- end
- end
-
- compute_response_get (req: WSF_REQUEST; res: WSF_RESPONSE; l_order: ORDER)
- local
- h: HTTP_HEADER
- l_msg : STRING
- etag_utils : ETAG_UTILS
- do
- create h.make
- create etag_utils
- h.put_content_type_application_json
- if attached {JSON_VALUE} json.value (l_order) as jv then
- l_msg := jv.representation
- h.put_content_length (l_msg.count)
- if attached req.request_time as time then
- h.add_header ("Date:" + time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
- end
- h.add_header ("etag:" + etag_utils.md5_digest (l_order.out))
- res.set_status_code ({HTTP_STATUS_CODE}.ok)
- res.put_header_text (h.string)
- res.put_string (l_msg)
- end
- end
-
- do_put (req: WSF_REQUEST; res: WSF_RESPONSE)
- -- Updating a resource with PUT
- -- A successful PUT request will not create a new resource, instead it will
- -- change the state of the resource identified by the current uri.
- -- If success we response with 200 and the updated order.
- -- 404 if the order is not found
- -- 400 in case of a bad request
- -- 500 internal server error
- -- If the request is a Conditional PUT, and it does not mat we response
- -- 415, precondition failed.
- local
- l_put: STRING
- l_order : detachable ORDER
- id : STRING
- do
- if attached req.path_info as l_path_info then
- id := get_order_id_from_path (l_path_info)
- l_put := retrieve_data (req)
- l_order := extract_order_request(l_put)
- if l_order /= Void and then db_access.orders.has_key (id) then
- l_order.set_id (id)
- if is_valid_to_update(l_order) then
- if is_conditional_put (req, l_order) then
- update_order( l_order)
- compute_response_put (req, res, l_order)
- else
- handle_precondition_fail_response ("", req, res)
- end
- else
- --| FIXME: Here we need to define the Allow methods
- handle_resource_conflict_response (l_put +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res)
- end
- else
- handle_bad_request_response (l_put +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res)
- end
- end
- end
-
- is_conditional_put (req : WSF_REQUEST; order : ORDER) : BOOLEAN
- -- Check if If-Match is present and then if there is a representation that has that etag
- -- if the representation hasn't changed, we return TRUE
- local
- etag_util : ETAG_UTILS
- do
- if attached retrieve_order (order.id) as l_order then
- if attached req.meta_string_variable ("HTTP_IF_MATCH") as if_match then
- create etag_util
- if if_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then
- Result := True
- end
- else
- Result := True
- end
- end
- end
-
-
- compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
- local
- h: HTTP_HEADER
- joc : JSON_ORDER_CONVERTER
- etag_utils : ETAG_UTILS
- do
- create h.make
- create joc.make
- create etag_utils
- json.add_converter(joc)
-
- create h.make
- h.put_content_type_application_json
- if attached req.request_time as time then
- h.add_header ("Date:" +time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
- end
- h.add_header ("etag:" + etag_utils.md5_digest (l_order.out))
- if attached {JSON_VALUE} json.value (l_order) as jv then
- h.put_content_length (jv.representation.count)
- res.set_status_code ({HTTP_STATUS_CODE}.ok)
- res.put_header_text (h.string)
- res.put_string (jv.representation)
- end
- end
-
-
- do_delete (req: WSF_REQUEST; res: WSF_RESPONSE)
- -- Here we use DELETE to cancel an order, if that order is in state where
- -- it can still be canceled.
- -- 200 if is ok
- -- 404 Resource not found
- -- 405 if consumer and service's view of the resouce state is inconsisent
- -- 500 if we have an internal server error
- local
- id: STRING
- do
- if attached req.path_info as l_path_info then
- id := get_order_id_from_path (l_path_info)
- if db_access.orders.has_key (id) then
- if is_valid_to_delete (id) then
- delete_order( id)
- compute_response_delete (req, res)
- else
- --| FIXME: Here we need to define the Allow methods
- handle_method_not_allowed_response (l_path_info + "%N There is conflict while trying to delete the order, the order could not be deleted in the current state", req, res)
- end
- else
- handle_resource_not_found_response (l_path_info + " not found in this server", req, res)
- end
- end
- end
-
- compute_response_delete (req: WSF_REQUEST; res: WSF_RESPONSE)
- local
- h : HTTP_HEADER
- do
- create h.make
- h.put_content_type_application_json
- if attached req.request_time as time then
- h.put_utc_date (time)
- end
- res.set_status_code ({HTTP_STATUS_CODE}.no_content)
- res.put_header_text (h.string)
- end
-
- do_post (req: WSF_REQUEST; res: WSF_RESPONSE)
- -- Here the convention is the following.
- -- POST is used for creation and the server determines the URI
- -- of the created resource.
- -- If the request post is SUCCESS, the server will create the order and will response with
- -- HTTP_RESPONSE 201 CREATED, the Location header will contains the newly created order's URI
- -- if the request post is not SUCCESS, the server will response with
- -- HTTP_RESPONSE 400 BAD REQUEST, the client send a bad request
- -- HTTP_RESPONSE 500 INTERNAL_SERVER_ERROR, when the server can deliver the request
- local
- l_post: STRING
- do
- l_post := retrieve_data (req)
- if attached extract_order_request (l_post) as l_order then
- save_order (l_order)
- compute_response_post (req, res, l_order)
- else
- handle_bad_request_response (l_post +"%N is not a valid ORDER", req, res)
- end
- end
-
- compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
- local
- h: HTTP_HEADER
- l_msg : STRING
- l_location : STRING
- joc : JSON_ORDER_CONVERTER
- do
- create h.make
-
- create joc.make
- json.add_converter(joc)
-
- h.put_content_type_application_json
- if attached {JSON_VALUE} json.value (l_order) as jv then
- l_msg := jv.representation
- h.put_content_length (l_msg.count)
- l_location := req.absolute_script_url (req.request_uri + "/" + l_order.id)
- h.put_location (l_location)
- if attached req.request_time as time then
- h.put_utc_date (time)
- end
- res.set_status_code ({HTTP_STATUS_CODE}.created)
- res.put_header_text (h.string)
- res.put_string (l_msg)
- end
- end
-
-feature {NONE} -- URI helper methods
-
- get_order_id_from_path (a_path: READABLE_STRING_32) : STRING
- do
- Result := a_path.split ('/').at (3)
- end
-
-feature {NONE} -- Implementation Repository Layer
-
- retrieve_order ( id : STRING) : detachable ORDER
- -- get the order by id if it exist, in other case, Void
- do
- Result := db_access.orders.item (id)
- end
-
- save_order (an_order: ORDER)
- -- save the order to the repository
- local
- i : INTEGER
- do
- from
- i := 1
- until
- not db_access.orders.has_key ((db_access.orders.count + i).out)
- loop
- i := i + 1
- end
- an_order.set_id ((db_access.orders.count + i).out)
- an_order.set_status ("submitted")
- an_order.add_revision
- db_access.orders.force (an_order, an_order.id)
- end
-
-
- is_valid_to_delete ( an_id : STRING) : BOOLEAN
- -- Is the order identified by `an_id' in a state whre it can still be deleted?
- do
- if attached retrieve_order (an_id) as l_order then
- if order_validation.is_state_valid_to_update (l_order.status) then
- Result := True
- end
- end
- end
-
- is_valid_to_update (an_order: ORDER) : BOOLEAN
- -- Check if there is a conflict while trying to update the order
- do
- if attached retrieve_order (an_order.id) as l_order then
- if order_validation.is_state_valid_to_update (l_order.status) and then order_validation.is_valid_status_state (an_order.status) and then
- order_validation.is_valid_transition (l_order, an_order.status) then
- Result := True
- end
- end
- end
-
- update_order (an_order: ORDER)
- -- update the order to the repository
- do
- an_order.add_revision
- db_access.orders.force (an_order, an_order.id)
- end
-
- delete_order (an_order: STRING)
- -- update the order to the repository
- do
- db_access.orders.remove (an_order)
- end
-
- extract_order_request (l_post : STRING) : detachable ORDER
- -- extract an object Order from the request, or Void
- -- if the request is invalid
- local
- parser : JSON_PARSER
- joc : JSON_ORDER_CONVERTER
- do
- create joc.make
- json.add_converter(joc)
- create parser.make_with_string (l_post)
- parser.parse_content
- if
- parser.is_valid and then
- attached parser.parsed_json_value as jv
- then
- if attached {like extract_order_request} json.object (jv, "ORDER") as res then
- Result := res
- end
- end
- end
-
-note
- copyright: "2011-2017, Javier Velilla and others"
- license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
-end
diff --git a/examples/restbucksCRUD/src/restbucks_server_execution.e b/examples/restbucksCRUD/src/restbucks_server_execution.e
deleted file mode 100644
index e1452bff..00000000
--- a/examples/restbucksCRUD/src/restbucks_server_execution.e
+++ /dev/null
@@ -1,57 +0,0 @@
-note
- description : "REST Buck server"
- date : "$Date$"
- revision : "$Revision$"
-
-class RESTBUCKS_SERVER_EXECUTION
-
-inherit
-
- WSF_ROUTED_SKELETON_EXECUTION
- undefine
- requires_proxy
- redefine
- initialize
- end
-
- WSF_ROUTED_URI_TEMPLATE_HELPER
-
- WSF_HANDLER_HELPER
-
- WSF_NO_PROXY_POLICY
-
-create
- make
-
-feature {NONE} -- Initialization
-
- initialize
- do
- Precursor
- initialize_router
- end
-
- setup_router
- local
- order_handler: ORDER_HANDLER
- doc: WSF_ROUTER_SELF_DOCUMENTATION_HANDLER
- do
- create order_handler.make_with_router (router)
- router.handle ("/order", order_handler, router.methods_POST)
- router.handle ("/order/{orderid}", order_handler, router.methods_GET + router.methods_DELETE + router.methods_PUT)
- create doc.make_hidden (router)
- router.handle ("/api/doc", doc, router.methods_GET)
- end
-
-
-note
- copyright: "2011-2015, Javier Velilla 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