Updated, improved and cleaned RESTbucks example.
Moved it under "rest" subfolder.
This commit is contained in:
@@ -7,13 +7,13 @@
|
|||||||
<exclude>/.svn$</exclude>
|
<exclude>/.svn$</exclude>
|
||||||
<exclude>/EIFGENs$</exclude>
|
<exclude>/EIFGENs$</exclude>
|
||||||
</file_rule>
|
</file_rule>
|
||||||
<option warning="true" void_safety="none">
|
<option warning="true" void_safety="all">
|
||||||
</option>
|
</option>
|
||||||
<setting name="console_application" value="true"/>
|
<setting name="console_application" value="true"/>
|
||||||
<setting name="concurrency" value="scoop"/>
|
<setting name="concurrency" value="scoop"/>
|
||||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||||
<library name="http_client" location="..\..\..\library\network\http_client\http_client.ecf"/>
|
<library name="http_client" location="..\..\..\..\library\network\http_client\http_client-safe.ecf"/>
|
||||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json.ecf"/>
|
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf"/>
|
||||||
<cluster name="src" location=".\src\" recursive="true"/>
|
<cluster name="src" location=".\src\" recursive="true"/>
|
||||||
</target>
|
</target>
|
||||||
</system>
|
</system>
|
||||||
177
examples/rest/restbucks_CRUD/client/src/restbuck_client.e
Normal file
177
examples/rest/restbucks_CRUD/client/src/restbuck_client.e
Normal file
@@ -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
|
||||||
@@ -6,100 +6,68 @@ the HTTP protocol as an application protocol instead of a transport protocol.
|
|||||||
Restbuck Protocol
|
Restbuck Protocol
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
<table>
|
* Method `POST` with URI `/order` : Create a new order, and upon success, receive a Locationheader specifying the new order's URI.
|
||||||
<TR><TH>Verb</TH> <TH>URI or template</TH> <TH>Use</TH></TR>
|
* Method `GET` with URI-template `/order/{orderId}` : Request the current state of the order specified by the URI.
|
||||||
<TR><TD>POST</TD> <TD>/order</TD> <TD>Create a new order, and upon success, receive a Locationheader specifying the new order's URI.</TD></TR>
|
* Method `PUT` with URI-template `/order/{orderId}` : Update an order at the given URI with new information, providing the full representation.
|
||||||
<TR><TD>GET</TD> <TD>/order/{orderId}</TD> <TD>Request the current state of the order specified by the URI.</TD></TR>
|
* Method `DELETE` with URI-tempalte `/order/{orderId}` : Logically remove the order identified by the given URI.
|
||||||
<TR><TD>PUT</TD> <TD>/order/{orderId}</TD> <TD>Update an order at the given URI with new information, providing the full representation.</TD></TR>
|
|
||||||
<TR><TD>DELETE</TD> <TD>/order/{orderId}</TD> <TD>Logically remove the order identified by the given URI.</TD></TR>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
Resource Represenation
|
Resource Represenation
|
||||||
----------------------
|
----------------------
|
||||||
The previous tables shows a contrat, the URI or URI template, allows us to indentify resources, now we will chose a
|
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.
|
representation, for this particular case we will use JSON.
|
||||||
|
|
||||||
Note: <br/>
|
Note:
|
||||||
1. *A resource can have multiple URIs*.<br/>
|
1. *A resource can have multiple URIs*.
|
||||||
2. *A resource can have multiple Representations*.<br/>
|
2. *A resource can have multiple Representations*.
|
||||||
|
|
||||||
RESTBUCKS_SERVER
|
RESTBUCKS_SERVER
|
||||||
----------------
|
----------------
|
||||||
This class implement the main entry of our REST CRUD service, we are using a default connector (Standalone Connector,
|
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).
|
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
|
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 encharge
|
table, the mapping is defined in the feature `setup_router`, this also show that the class `ORDER_HANDLER` will be in charge
|
||||||
of to handle different type of request to the ORDER resource.
|
of handling different types of request to the ORDER resource.
|
||||||
|
|
||||||
|
```
|
||||||
class
|
class RESTBUCKS_SERVER_EXECUTION
|
||||||
RESTBUCKS_SERVER
|
|
||||||
|
|
||||||
inherit
|
inherit
|
||||||
ANY
|
WSF_ROUTED_SKELETON_EXECUTION
|
||||||
|
undefine
|
||||||
|
requires_proxy
|
||||||
|
end
|
||||||
|
|
||||||
URI_TEMPLATE_ROUTED_SERVICE
|
WSF_NO_PROXY_POLICY
|
||||||
|
|
||||||
DEFAULT_SERVICE
|
|
||||||
-- Here we are using a default connector using the default Standalone Connector,
|
SHARED_RESTBUCKS_API
|
||||||
-- but it's possible to use other connector (CGI or FCGI).
|
|
||||||
|
|
||||||
create
|
create
|
||||||
make
|
make
|
||||||
|
|
||||||
feature {NONE} -- Initialization
|
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
|
setup_router
|
||||||
local
|
local
|
||||||
order_handler: ORDER_HANDLER [REQUEST_URI_TEMPLATE_HANDLER_CONTEXT]
|
doc: WSF_ROUTER_SELF_DOCUMENTATION_HANDLER
|
||||||
do
|
do
|
||||||
create order_handler
|
setup_order_handler (router)
|
||||||
router.map_with_request_methods ("/order", order_handler, <<"POST">>)
|
|
||||||
router.map_with_request_methods ("/order/{orderid}", order_handler, <<"GET", "DELETE", "PUT">>)
|
create doc.make_hidden (router)
|
||||||
|
router.handle ("/api/doc", doc, router.methods_GET)
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Execution
|
setup_order_handler (a_router: WSF_ROUTER)
|
||||||
|
|
||||||
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.
|
|
||||||
local
|
local
|
||||||
h : HTTP_HEADER
|
order_handler: ORDER_HANDLER
|
||||||
l_description : STRING
|
|
||||||
l_api_doc : STRING
|
|
||||||
do
|
do
|
||||||
if req.content_length_value > 0 then
|
create order_handler.make ("orderid", a_router)
|
||||||
req.input.read_string (req.content_length_value.as_integer_32)
|
router.handle ("/order", order_handler, router.methods_POST)
|
||||||
end
|
router.handle ("/order/{orderid}", order_handler, router.methods_GET + router.methods_DELETE + router.methods_PUT)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
How to Create an order with POST
|
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
|
if the request POST is not SUCCESS, the server will response with
|
||||||
400 BAD REQUEST, the client send a bad request or
|
400 BAD REQUEST, the client send a bad request or
|
||||||
500 INTERNAL_SERVER_ERROR, when the server can deliver the request.
|
500 INTERNAL_SERVER_ERROR, when the server can deliver the request.
|
||||||
|
```
|
||||||
POST /order HTTP/1.1
|
POST /order HTTP/1.1
|
||||||
Host: 127.0.0.1:8080
|
Host: 127.0.0.1:8080
|
||||||
Connection: keep-alive
|
Connection: keep-alive
|
||||||
@@ -134,9 +102,10 @@ if the request POST is not SUCCESS, the server will response with
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Response success
|
Response success
|
||||||
|
```
|
||||||
HTTP/1.1 201 Created
|
HTTP/1.1 201 Created
|
||||||
Status 201 Created
|
Status 201 Created
|
||||||
Content-Type application/json
|
Content-Type application/json
|
||||||
@@ -154,6 +123,7 @@ Response success
|
|||||||
"option" : "skim"
|
"option" : "skim"
|
||||||
} ]
|
} ]
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
note:
|
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
|
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 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
|
If is a Conditional GET and the resource does not change we send a 304, Resource not modifed
|
||||||
|
|
||||||
|
```
|
||||||
GET /order/1 HTTP/1.1
|
GET /order/1 HTTP/1.1
|
||||||
Host: 127.0.0.1:8080
|
Host: 127.0.0.1:8080
|
||||||
Connection: keep-alive
|
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-Language: es-419,es;q=0.8,en;q=0.6
|
||||||
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
|
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
|
||||||
If-None-Match: 6542EF270D91D3EAF39CFB382E4CEBA7
|
If-None-Match: 6542EF270D91D3EAF39CFB382E4CEBA7
|
||||||
|
```
|
||||||
|
|
||||||
Response
|
Response
|
||||||
|
```
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
|
|
||||||
Status 200 OK
|
Status 200 OK
|
||||||
@@ -194,6 +167,7 @@ Response
|
|||||||
"option" : "skim"
|
"option" : "skim"
|
||||||
} ]
|
} ]
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
note:
|
note:
|
||||||
curl -vv http://localhost:9090/order/1
|
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_
|
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.
|
But we change our decision and we want to stay in the shop.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
PUT /order/1 HTTP/1.1
|
PUT /order/1 HTTP/1.1
|
||||||
Content-Length: 122
|
Content-Length: 122
|
||||||
Content-Type: application/json; charset=UTF-8
|
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"
|
"option" : "skim"
|
||||||
} ]
|
} ]
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Response success
|
Response success
|
||||||
|
```
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Status 200 OK
|
Status 200 OK
|
||||||
Content-Type application/json
|
Content-Type application/json
|
||||||
@@ -250,6 +223,7 @@ Response success
|
|||||||
"option" : "skim"
|
"option" : "skim"
|
||||||
} ]
|
} ]
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
How to Delete an order with DELETE
|
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
|
405 if consumer and service's view of the resouce state is inconsisent
|
||||||
500 if we have an internal server error
|
500 if we have an internal server error
|
||||||
|
|
||||||
|
```
|
||||||
DELETE /order/1 HTTP/1.1
|
DELETE /order/1 HTTP/1.1
|
||||||
Host: localhost:8080
|
Host: localhost:8080
|
||||||
Connection: Keep-Alive
|
Connection: Keep-Alive
|
||||||
|
```
|
||||||
|
|
||||||
Response success
|
Response success
|
||||||
|
|
||||||
|
```
|
||||||
HTTP/1.1 204 No Content
|
HTTP/1.1 204 No Content
|
||||||
|
|
||||||
Status 204 No Content
|
Status 204 No Content
|
||||||
Content-Type application/json
|
Content-Type application/json
|
||||||
Date FRI,09 DEC 2011 21:10:51.00 GMT
|
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
|
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
|
404 No Found
|
||||||
|
|
||||||
|
```
|
||||||
GET /order/1 HTTP/1.1
|
GET /order/1 HTTP/1.1
|
||||||
Host: localhost:8080
|
Host: localhost:8080
|
||||||
Connection: Keep-Alive
|
Connection: Keep-Alive
|
||||||
|
```
|
||||||
|
|
||||||
Response
|
Response
|
||||||
|
|
||||||
|
```
|
||||||
HTTP/1.1 404 Not Found
|
HTTP/1.1 404 Not Found
|
||||||
|
|
||||||
Status 404 Not Found
|
Status 404 Not Found
|
||||||
@@ -289,9 +269,10 @@ Response
|
|||||||
Date FRI,09 DEC 2011 21:14:17.79 GMT
|
Date FRI,09 DEC 2011 21:14:17.79 GMT
|
||||||
|
|
||||||
The following resource/order/1 is not found
|
The following resource/order/1 is not found
|
||||||
|
```
|
||||||
|
|
||||||
References
|
References
|
||||||
----------
|
----------
|
||||||
1. [How to get a cup of coffe](http://www.infoq.com/articles/webber-rest-workflow)
|
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)
|
||||||
|
|
||||||
@@ -9,19 +9,19 @@
|
|||||||
<option void_safety="all">
|
<option void_safety="all">
|
||||||
</option>
|
</option>
|
||||||
<setting name="console_application" value="true"/>
|
<setting name="console_application" value="true"/>
|
||||||
<setting name="concurrency" value="none"/>
|
<setting name="concurrency" value="scoop"/>
|
||||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||||
<library name="conneg" location="..\..\library\network\protocol\content_negotiation\conneg-safe.ecf"/>
|
<library name="conneg" location="..\..\..\library\network\protocol\content_negotiation\conneg-safe.ecf"/>
|
||||||
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto-safe.ecf" readonly="false"/>
|
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto-safe.ecf" readonly="false"/>
|
||||||
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf" readonly="false"/>
|
<library name="default_standalone" location="..\..\..\library\server\wsf\default\standalone-safe.ecf" readonly="false"/>
|
||||||
<library name="encoder" location="..\..\library\text\encoder\encoder-safe.ecf" readonly="false"/>
|
<library name="encoder" location="..\..\..\library\text\encoder\encoder-safe.ecf" readonly="false"/>
|
||||||
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf" readonly="false"/>
|
<library name="http" location="..\..\..\library\network\protocol\http\http-safe.ecf" readonly="false"/>
|
||||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf"/>
|
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf"/>
|
||||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||||
<library name="uri_template" location="..\..\library\text\parser\uri_template\uri_template-safe.ecf" readonly="false"/>
|
<library name="uri_template" location="..\..\..\library\text\parser\uri_template\uri_template-safe.ecf" readonly="false"/>
|
||||||
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf" readonly="false"/>
|
<library name="wsf" location="..\..\..\library\server\wsf\wsf-safe.ecf" readonly="false"/>
|
||||||
<library name="wsf_extension" location="..\..\library\server\wsf\wsf_extension-safe.ecf" readonly="false"/>
|
<library name="wsf_extension" location="..\..\..\library\server\wsf\wsf_extension-safe.ecf" readonly="false"/>
|
||||||
</target>
|
</target>
|
||||||
<target name="restbucks" extends="restbucks_common">
|
<target name="restbucks" extends="restbucks_common">
|
||||||
<root class="RESTBUCKS_SERVER" feature="make"/>
|
<root class="RESTBUCKS_SERVER" feature="make"/>
|
||||||
@@ -41,10 +41,10 @@
|
|||||||
<debug name="standalone" enabled="true"/>
|
<debug name="standalone" enabled="true"/>
|
||||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||||
</option>
|
</option>
|
||||||
<library name="wsf_policy_driven" location="..\..\library\server\wsf\wsf_policy_driven-safe.ecf" readonly="false"/>
|
<library name="wsf_policy_driven" location="..\..\..\library\server\wsf\wsf_policy_driven-safe.ecf" readonly="false"/>
|
||||||
<cluster name="src" location="src\" recursive="true">
|
<cluster name="src" location="src\" recursive="true">
|
||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/resource$</exclude>
|
<exclude>/resource/order_handler.e$</exclude>
|
||||||
</file_rule>
|
</file_rule>
|
||||||
</cluster>
|
</cluster>
|
||||||
</target>
|
</target>
|
||||||
@@ -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
|
||||||
|
-- <Precursor/>.
|
||||||
|
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
|
||||||
44
examples/rest/restbucks_CRUD/src/database/basic_database.e
Normal file
44
examples/rest/restbucks_CRUD/src/database/basic_database.e
Normal file
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
173
examples/rest/restbucks_CRUD/src/database/restbucks_api.e
Normal file
173
examples/rest/restbucks_CRUD/src/database/restbucks_api.e
Normal file
@@ -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
|
||||||
@@ -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
|
||||||
135
examples/rest/restbucks_CRUD/src/domain/order.e
Normal file
135
examples/rest/restbucks_CRUD/src/domain/order.e
Normal file
@@ -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
|
||||||
93
examples/rest/restbucks_CRUD/src/domain/order_item.e
Normal file
93
examples/rest/restbucks_CRUD/src/domain/order_item.e
Normal file
@@ -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
|
||||||
@@ -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
|
||||||
@@ -10,14 +10,10 @@ inherit
|
|||||||
|
|
||||||
WSF_SKELETON_HANDLER
|
WSF_SKELETON_HANDLER
|
||||||
|
|
||||||
SHARED_DATABASE_API
|
SHARED_RESTBUCKS_API
|
||||||
|
|
||||||
SHARED_EJSON
|
|
||||||
|
|
||||||
REFACTORING_HELPER
|
REFACTORING_HELPER
|
||||||
|
|
||||||
SHARED_ORDER_VALIDATION
|
|
||||||
|
|
||||||
WSF_RESOURCE_HANDLER_HELPER
|
WSF_RESOURCE_HANDLER_HELPER
|
||||||
rename
|
rename
|
||||||
execute_options as helper_execute_options,
|
execute_options as helper_execute_options,
|
||||||
@@ -25,9 +21,17 @@ inherit
|
|||||||
end
|
end
|
||||||
|
|
||||||
create
|
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
|
feature -- Execution variables
|
||||||
|
|
||||||
@@ -155,26 +159,21 @@ feature -- Access
|
|||||||
-- If `a_strong' then the strong comparison function must be used.
|
-- If `a_strong' then the strong comparison function must be used.
|
||||||
local
|
local
|
||||||
l_id: STRING
|
l_id: STRING
|
||||||
l_etag_util: ETAG_UTILS
|
|
||||||
do
|
do
|
||||||
l_id := order_id_from_request (req)
|
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
|
||||||
check attached db_access.orders.item (l_id) as l_order then
|
check attached order (l_id) as l_order then
|
||||||
-- postcondition of `has_key'
|
-- postcondition of `has_key'
|
||||||
create l_etag_util
|
Result := a_etag.same_string (order_etag (l_order))
|
||||||
Result := a_etag.same_string (l_etag_util.md5_digest (l_order.out).as_string_32)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
etag (req: WSF_REQUEST): detachable READABLE_STRING_8
|
etag (req: WSF_REQUEST): detachable READABLE_STRING_8
|
||||||
-- Optional Etag for `req' in the requested variant
|
-- Optional Etag for `req' in the requested variant
|
||||||
local
|
|
||||||
l_etag_utils: ETAG_UTILS
|
|
||||||
do
|
do
|
||||||
create l_etag_utils
|
|
||||||
if attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -232,10 +231,10 @@ feature -- Execution
|
|||||||
else
|
else
|
||||||
-- the request is of the form /order/{orderid}
|
-- the request is of the form /order/{orderid}
|
||||||
l_id := order_id_from_request (req)
|
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
|
a_helper.set_resource_exists
|
||||||
if req.is_get_head_request_method then
|
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'
|
-- postcondition `item_if_found' of `has_key'
|
||||||
req.set_execution_variable (Order_execution_variable, l_order)
|
req.set_execution_variable (Order_execution_variable, l_order)
|
||||||
end
|
end
|
||||||
@@ -259,7 +258,7 @@ feature -- GET/HEAD content
|
|||||||
do
|
do
|
||||||
check attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then
|
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
|
-- 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)
|
req.set_execution_variable (Generated_content_execution_variable, jv.representation)
|
||||||
else
|
else
|
||||||
req.set_execution_variable (Generated_content_execution_variable, "")
|
req.set_execution_variable (Generated_content_execution_variable, "")
|
||||||
@@ -300,13 +299,13 @@ feature -- DELETE
|
|||||||
|
|
||||||
delete (req: WSF_REQUEST)
|
delete (req: WSF_REQUEST)
|
||||||
-- Delete resource named in `req' or set an error on `req.error_handler'.
|
-- Delete resource named in `req' or set an error on `req.error_handler'.
|
||||||
local
|
|
||||||
l_id: STRING
|
|
||||||
do
|
do
|
||||||
l_id := order_id_from_request (req)
|
if
|
||||||
if db_access.orders.has_key (l_id) then
|
attached order_id_from_request (req) as l_id and then
|
||||||
if is_valid_to_delete (l_id) then
|
attached order (l_id) as l_order
|
||||||
delete_order (l_id)
|
then
|
||||||
|
if is_valid_to_delete (l_order) then
|
||||||
|
delete_order (l_order)
|
||||||
else
|
else
|
||||||
req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.method_not_allowed, "DELETE not valid",
|
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")
|
"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.
|
-- Implementor must set error code of 200 OK or 204 No Content or 303 See Other or 500 Server Error.
|
||||||
do
|
do
|
||||||
if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then
|
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)
|
compute_response_post (req, res, l_order)
|
||||||
else
|
else
|
||||||
handle_bad_request_response ("Not a valid order", req, res)
|
handle_bad_request_response ("Not a valid order", req, res)
|
||||||
@@ -386,10 +385,10 @@ feature -- PUT/POST
|
|||||||
l_id: STRING
|
l_id: STRING
|
||||||
do
|
do
|
||||||
if attached {READABLE_STRING_8} req.execution_variable (Request_entity_execution_variable) as l_request then
|
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
|
if req.is_put_request_method then
|
||||||
l_id := order_id_from_request (req)
|
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)
|
l_order.set_id (l_id)
|
||||||
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0)
|
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0)
|
||||||
req.set_execution_variable (Extracted_order_execution_variable, l_order)
|
req.set_execution_variable (Extracted_order_execution_variable, l_order)
|
||||||
@@ -422,153 +421,139 @@ feature -- PUT/POST
|
|||||||
feature -- HTTP Methods
|
feature -- HTTP Methods
|
||||||
|
|
||||||
compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
|
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
|
do
|
||||||
create h.make
|
res.header.put_content_type_application_json
|
||||||
create joc.make
|
res.header.put_utc_date (create {DATE_TIME}.make_now_utc)
|
||||||
create etag_utils
|
res.header.add_header ("etag:" + order_etag (l_order))
|
||||||
json.add_converter(joc)
|
if attached order_to_json (l_order) as jv then
|
||||||
|
res.header.put_content_length (jv.representation.count)
|
||||||
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.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||||
res.put_header_text (h.string)
|
|
||||||
res.put_string (jv.representation)
|
res.put_string (jv.representation)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
|
compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
|
||||||
local
|
local
|
||||||
h: HTTP_HEADER
|
|
||||||
l_msg : STRING
|
l_msg : STRING
|
||||||
l_location : STRING
|
|
||||||
joc : JSON_ORDER_CONVERTER
|
|
||||||
do
|
do
|
||||||
create h.make
|
res.header.put_content_type_application_json
|
||||||
|
if attached order_to_json (l_order) as jv then
|
||||||
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
|
l_msg := jv.representation
|
||||||
h.put_content_length (l_msg.count)
|
res.header.put_content_length (l_msg.count)
|
||||||
if attached req.http_host as host then
|
res.header.put_location (req.absolute_script_url (req.request_uri + "/" + l_order.id))
|
||||||
l_location := "http://" + host + req.request_uri + "/" + l_order.id
|
res.header.put_utc_date (create {DATE_TIME}.make_now_utc)
|
||||||
h.put_location (l_location)
|
|
||||||
end
|
|
||||||
if attached req.request_time as time then
|
|
||||||
h.put_utc_date (time)
|
|
||||||
end
|
|
||||||
res.set_status_code ({HTTP_STATUS_CODE}.created)
|
res.set_status_code ({HTTP_STATUS_CODE}.created)
|
||||||
res.put_header_text (h.string)
|
|
||||||
res.put_string (l_msg)
|
res.put_string (l_msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
feature {NONE} -- URI helper methods
|
feature {NONE} -- URI helper methods
|
||||||
|
|
||||||
order_id_from_request (req: WSF_REQUEST): STRING
|
order_id_from_request (req: WSF_REQUEST): detachable STRING_8
|
||||||
-- Value of "orderid" template URI variable in `req'
|
|
||||||
require
|
|
||||||
req_attached: req /= Void
|
|
||||||
do
|
do
|
||||||
if attached {WSF_VALUE} req.path_parameter ("orderid") as l_value then
|
if attached {WSF_STRING} req.path_parameter (orderid_path_parameter_name) as p_id then
|
||||||
Result := l_value.as_string.value.as_string_8
|
Result := p_id.url_encoded_value -- the ORDER id has to be valid STRING 8 value.
|
||||||
else
|
|
||||||
Result := ""
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
feature {NONE} -- Implementation Repository Layer
|
feature {NONE} -- Implementation Repository Layer
|
||||||
|
|
||||||
retrieve_order ( id : STRING) : detachable ORDER
|
is_valid_to_delete (a_order: ORDER): BOOLEAN
|
||||||
-- get the order by id if it exist, in other case, Void
|
-- Is the order identified by `a_id' in a state whre it can still be deleted?
|
||||||
do
|
do
|
||||||
Result := db_access.orders.item (id)
|
if attached order (a_order.id) as l_order then
|
||||||
end
|
if api.is_state_valid_to_update (l_order.status) then
|
||||||
|
|
||||||
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
|
Result := True
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
is_valid_to_update (an_order: ORDER) : BOOLEAN
|
is_valid_to_update (a_order: ORDER): BOOLEAN
|
||||||
-- Check if there is a conflict while trying to update the order
|
-- Check if there is a conflict while trying to update the order.
|
||||||
do
|
do
|
||||||
if attached retrieve_order (an_order.id) as l_order then
|
if attached order (a_order.id) as l_existing_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
|
if
|
||||||
order_validation.is_valid_transition (l_order, an_order.status) then
|
api.is_state_valid_to_update (l_existing_order.status) and then
|
||||||
Result := True
|
api.is_valid_status_state (a_order.status) and then
|
||||||
end
|
api.is_valid_transition (l_existing_order, a_order.status)
|
||||||
end
|
then
|
||||||
end
|
Result := True
|
||||||
|
|
||||||
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
|
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
|
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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
end
|
end
|
||||||
356
examples/rest/restbucks_CRUD/src/resource/order_handler.e
Normal file
356
examples/rest/restbucks_CRUD/src/resource/order_handler.e
Normal file
@@ -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)
|
||||||
|
-- <Precursor>
|
||||||
|
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
|
||||||
@@ -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 ("<h1>RESTbucks example (a Create-Read-Update-Delete REST API)</h1>%N")
|
||||||
|
l_html.append ("<ul>")
|
||||||
|
l_html.append ("<li>To test this example: create <a href=%"/new_order%">new ORDER entry</a> , and get its json representation right away.</li>")
|
||||||
|
l_html.append ("<li>See the auto-generated <a href=%"/api/doc%">documentation</a>.</li>")
|
||||||
|
l_html.append ("</ul>")
|
||||||
|
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
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
|
||||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="client" uuid="D0059CEB-5F5C-4D21-8C71-842BD0F88468">
|
|
||||||
<target name="client">
|
|
||||||
<root class="RESTBUCK_CLIENT" feature="make"/>
|
|
||||||
<file_rule>
|
|
||||||
<exclude>/.git$</exclude>
|
|
||||||
<exclude>/.svn$</exclude>
|
|
||||||
<exclude>/EIFGENs$</exclude>
|
|
||||||
</file_rule>
|
|
||||||
<option warning="true" void_safety="all">
|
|
||||||
</option>
|
|
||||||
<setting name="console_application" value="true"/>
|
|
||||||
<setting name="concurrency" value="scoop"/>
|
|
||||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
|
||||||
<library name="http_client" location="..\..\..\library\network\http_client\http_client-safe.ecf" readonly="false"/>
|
|
||||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf"/>
|
|
||||||
<cluster name="src" location=".\src\" recursive="true"/>
|
|
||||||
</target>
|
|
||||||
</system>
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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)
|
|
||||||
-- <Precursor>
|
|
||||||
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
|
|
||||||
@@ -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
|
|
||||||
Reference in New Issue
Block a user