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_html.append ("
  • See the auto-generated documentation.
  • ") + l_html.append ("
") + 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