diff --git a/My-Shop/README.md b/My-Shop/README.md
new file mode 100644
index 0000000..c0a3a37
--- /dev/null
+++ b/My-Shop/README.md
@@ -0,0 +1 @@
+A Sample Shopping Website (for training purposes)
diff --git a/My-Shop/go-backend/set-go-path.sh b/My-Shop/go-backend/set-go-path.sh
new file mode 100644
index 0000000..8a704b2
--- /dev/null
+++ b/My-Shop/go-backend/set-go-path.sh
@@ -0,0 +1,8 @@
+# You have to source this file from your shell !
+#
+# . set-go-path.sh
+#
+
+export GOPATH="$PWD"
+echo "GOPATH=$GOPATH"
+
diff --git a/My-Shop/go-backend/src/.gitignore b/My-Shop/go-backend/src/.gitignore
new file mode 100644
index 0000000..002c01e
--- /dev/null
+++ b/My-Shop/go-backend/src/.gitignore
@@ -0,0 +1 @@
+code.google.com
diff --git a/My-Shop/go-backend/src/itix.fr/rest-backend/main.go b/My-Shop/go-backend/src/itix.fr/rest-backend/main.go
new file mode 100644
index 0000000..607c802
--- /dev/null
+++ b/My-Shop/go-backend/src/itix.fr/rest-backend/main.go
@@ -0,0 +1,208 @@
+package main
+
+import (
+ "code.google.com/p/gorest"
+ "net/http"
+ "fmt"
+ "strings"
+ "io/ioutil"
+ "encoding/json"
+)
+
+type Category struct {
+ Id string
+ Name string
+}
+
+var Categories []Category = []Category {
+ { "fringues", "Habillage" },
+ { "cuisine", "Cuisine" },
+ { "digital", "Digital" },
+ { "maison", "Bricolage" } }
+
+type Product struct {
+ Id int
+ Name string
+ Category string
+ Image string
+ Description string
+ Price float32
+ Stock int
+ VendorId string
+ VendorName string
+ VendorProductId string
+ IsDigital bool
+}
+
+type BuyResponse struct {
+ ResponseCode string
+ DownloadUrl string
+}
+
+type CallbackResponse struct {
+ ResponseCode string `json:"code"`
+ RedirectUrl string `json:"redirect_url"`
+}
+
+type BuyCallback struct {
+ VendorId string
+ VendorName string
+ VendorProductId string
+}
+
+var Products []Product = []Product {
+ { 0, "T-Shirt", "fringues", "brice.jpg", "Le T-Shirt de Brice de Nice.", 99.9, 1, "", "", "", false },
+ { 1, "Pull col roulé", "fringues", "pull.jpg", "Un pull à col roulé de couleur marron.", 2.00, 10, "", "", "", false },
+ { 2, "Cocotte minute", "cuisine", "cocotte.jpg", "La cocotte minute 'Presto'.", 45.00, 2, "", "", "", false },
+ { 3, "Marteau-Piqueur", "maison", "mp.jpg", "Le marteau piqueur 'DESTRUCTOR 2000'.", 600, 5, "", "", "", false } }
+// { 4, "Visseuse Ultrasonique", "maison", "visseuse.jpg", "La visseuse-dévisseuse de chez Méga Store.", 600, 5, "mega-store", "Méga Store", "0001", false },
+// { 5, "DVD de Harry Poter", "digital", "dvd.jpg", "L'histoire de Ari l'empotteur au pays des merveilles.", 29.9, -1, "zouba-books", "Zouba Books", "12345", true } }
+
+func main() {
+ gorest.RegisterService(new(MyShopService)) // Register our service
+ http.Handle("/api/",gorest.Handle())
+ http.Handle("/", http.FileServer(http.Dir("www-root")))
+ http.ListenAndServe(":8787", nil)
+}
+
+// REST Service Definition
+type MyShopService struct {
+ gorest.RestService `root:"/api/" consumes:"application/json" produces:"application/json"`
+ getCategories gorest.EndPoint `method:"GET" path:"/shop/category/" output:"[]Category"`
+ getProductsByCategory gorest.EndPoint `method:"GET" path:"/shop/product/?{category:string}" output:"[]Product"`
+ searchProducts gorest.EndPoint `method:"GET" path:"/shop/search/{criteria:string}" output:"[]Product"`
+ getProduct gorest.EndPoint `method:"GET" path:"/shop/product/{id:int}" output:"Product"`
+ addProduct gorest.EndPoint `method:"POST" path:"/market/product/" postdata:"Product"`
+ buyProduct gorest.EndPoint `method:"GET" path:"/shop/product/{id:int}/buy" output:"BuyResponse"`
+}
+
+func(serv MyShopService) SearchProducts(criteria string) []Product {
+ fmt.Println(">>> SearchProducts: criteria = ", criteria)
+ if (criteria == "") {
+ return Products
+ }
+
+ sliceofcriteria := strings.Split(criteria, " ")
+
+ FilteredProducts := []Product {}
+ for _, p := range Products {
+ var selected bool = false
+ name := strings.ToLower(p.Name)
+ desc := strings.ToLower(p.Description)
+
+ for _, c := range sliceofcriteria {
+ c = strings.ToLower(c)
+ if strings.Contains(name, c) || strings.Contains(desc, c) {
+ selected = true
+ }
+ }
+
+ if selected {
+ FilteredProducts = append(FilteredProducts, p)
+ }
+ }
+
+ return FilteredProducts
+}
+
+func(serv MyShopService) GetProducts() []Product {
+ fmt.Println(">>> GetProducts")
+ return Products
+}
+
+func(serv MyShopService) BuyProduct(id int) (resp BuyResponse) {
+ fmt.Println(">>> BuyProduct: id = ", id)
+ if id > len(Products) - 1 {
+ serv.ResponseBuilder().SetResponseCode(404).Overide(true)
+ return
+ }
+
+ if Products[id].Stock == 0 {
+ serv.ResponseBuilder().SetResponseCode(409).Overide(true)
+ return
+ }
+
+ if Products[id].Stock >0 {
+ Products[id].Stock--
+ }
+
+ return_code := "order-accepted"
+ redirect_url := ""
+ if Products[id].VendorId != "" {
+ elements := []string { "http://api.the-vendor.test:8080/api/vendor", Products[id].VendorId, "callback", Products[id].VendorProductId, "1" }
+ callback := strings.Join(elements, "/")
+ fmt.Println(">>> BuyProduct: firing callback to vendor", Products[id].VendorName, "with URL =", callback)
+
+ client := &http.Client{}
+ req, err := http.NewRequest("GET", callback, nil)
+ resp, err := client.Do(req)
+ if err != nil {
+ fmt.Println(">>> BuyProduct: ERROR", err)
+ return_code = "error"
+ serv.ResponseBuilder().SetResponseCode(500).Overide(true)
+ } else {
+ defer resp.Body.Close()
+ fmt.Println(">>> BuyProduct: response Status:", resp.Status)
+ if resp.Status != "200 OK" {
+ body, _ := ioutil.ReadAll(resp.Body)
+ fmt.Println(">>> BuyProduct: response Body:", string(body))
+ return_code = "error"
+ serv.ResponseBuilder().SetResponseCode(500).Overide(true)
+ } else {
+ if Products[id].IsDigital {
+ fmt.Println(">>> BuyProduct: Decoding JSON response")
+ json_resp := new(CallbackResponse)
+ json.NewDecoder(resp.Body).Decode(json_resp)
+ redirect_url = json_resp.RedirectUrl;
+ } else {
+ fmt.Println(">>> BuyProduct: Ignoring JSON response")
+ }
+ }
+ }
+ }
+
+ fmt.Println(">>> BuyProduct: return_code =", return_code, "redirect_url =", redirect_url)
+
+ resp = BuyResponse { return_code, redirect_url }
+ return
+}
+
+
+func(serv MyShopService) GetProduct(id int) (p Product) {
+ fmt.Println(">>> GetProduct: id =", id)
+ if id > len(Products) - 1 {
+ serv.ResponseBuilder().SetResponseCode(404).Overide(true)
+ return
+ }
+ p = Products[id]
+ return
+}
+
+func(serv MyShopService) AddProduct(posted Product) {
+ fmt.Println(">>> AddProduct: posted =", posted)
+ id := len(Products)
+ posted.Id = id
+ Products = append(Products, posted);
+ serv.ResponseBuilder().Created("/api/shop/product/"+string(id))
+}
+
+func(serv MyShopService) GetProductsByCategory(category string) []Product {
+ fmt.Println(">>> GetProductsByCategory: category =", category)
+ if (category == "") {
+ return Products
+ }
+
+ FilteredProducts := []Product {}
+ for _, p := range Products {
+ if p.Category == category {
+ FilteredProducts = append(FilteredProducts, p)
+ }
+ }
+
+ return FilteredProducts
+}
+
+func(serv MyShopService) GetCategories() []Category {
+ fmt.Println(">>> GetCategories")
+ return Categories
+}
diff --git a/My-Shop/go-backend/test/create-product.sh b/My-Shop/go-backend/test/create-product.sh
new file mode 100644
index 0000000..afe7268
--- /dev/null
+++ b/My-Shop/go-backend/test/create-product.sh
@@ -0,0 +1,6 @@
+curl -H "Content-Type: application/json" \
+ -d '{ "Name": "A New Product", "Category": "maison", "Image": "logo.png", "Description": "My brand new product", "Price": 1, "Stock": 1 }' \
+ -X POST \
+ -D - \
+ http://localhost:8787/api/shop/product/
+
diff --git a/My-Shop/go-backend/www-root/.gitignore b/My-Shop/go-backend/www-root/.gitignore
new file mode 100644
index 0000000..e43b0f9
--- /dev/null
+++ b/My-Shop/go-backend/www-root/.gitignore
@@ -0,0 +1 @@
+.DS_Store
diff --git a/My-Shop/go-backend/www-root/book-1234.pdf b/My-Shop/go-backend/www-root/book-1234.pdf
new file mode 100644
index 0000000..2a7ae62
Binary files /dev/null and b/My-Shop/go-backend/www-root/book-1234.pdf differ
diff --git a/My-Shop/go-backend/www-root/img/bluray.jpg b/My-Shop/go-backend/www-root/img/bluray.jpg
new file mode 100644
index 0000000..c6d0085
Binary files /dev/null and b/My-Shop/go-backend/www-root/img/bluray.jpg differ
diff --git a/My-Shop/go-backend/www-root/img/brice.jpg b/My-Shop/go-backend/www-root/img/brice.jpg
new file mode 100644
index 0000000..70e382c
Binary files /dev/null and b/My-Shop/go-backend/www-root/img/brice.jpg differ
diff --git a/My-Shop/go-backend/www-root/img/cocotte.jpg b/My-Shop/go-backend/www-root/img/cocotte.jpg
new file mode 100644
index 0000000..a59ef98
Binary files /dev/null and b/My-Shop/go-backend/www-root/img/cocotte.jpg differ
diff --git a/My-Shop/go-backend/www-root/img/dvd.jpg b/My-Shop/go-backend/www-root/img/dvd.jpg
new file mode 100644
index 0000000..966dd50
Binary files /dev/null and b/My-Shop/go-backend/www-root/img/dvd.jpg differ
diff --git a/My-Shop/go-backend/www-root/img/livre.jpg b/My-Shop/go-backend/www-root/img/livre.jpg
new file mode 100644
index 0000000..f2b9bad
Binary files /dev/null and b/My-Shop/go-backend/www-root/img/livre.jpg differ
diff --git a/My-Shop/go-backend/www-root/img/logo.png b/My-Shop/go-backend/www-root/img/logo.png
new file mode 100644
index 0000000..e0bb104
Binary files /dev/null and b/My-Shop/go-backend/www-root/img/logo.png differ
diff --git a/My-Shop/go-backend/www-root/img/mp.jpg b/My-Shop/go-backend/www-root/img/mp.jpg
new file mode 100644
index 0000000..d4a4e96
Binary files /dev/null and b/My-Shop/go-backend/www-root/img/mp.jpg differ
diff --git a/My-Shop/go-backend/www-root/img/pull.jpg b/My-Shop/go-backend/www-root/img/pull.jpg
new file mode 100644
index 0000000..845a1d6
Binary files /dev/null and b/My-Shop/go-backend/www-root/img/pull.jpg differ
diff --git a/My-Shop/go-backend/www-root/img/visseuse.jpg b/My-Shop/go-backend/www-root/img/visseuse.jpg
new file mode 100644
index 0000000..34ea520
Binary files /dev/null and b/My-Shop/go-backend/www-root/img/visseuse.jpg differ
diff --git a/My-Shop/go-backend/www-root/index.html b/My-Shop/go-backend/www-root/index.html
new file mode 100644
index 0000000..b34f6fb
--- /dev/null
+++ b/My-Shop/go-backend/www-root/index.html
@@ -0,0 +1,31 @@
+
+
+
+ My Shop
+
+
+
+
+
+
+
+
+
+
+
+
Boutique
+ Subtitle
+
+
+
+
+
+
+
+
diff --git a/My-Shop/go-backend/www-root/js/.gitignore b/My-Shop/go-backend/www-root/js/.gitignore
new file mode 100644
index 0000000..3a3b630
--- /dev/null
+++ b/My-Shop/go-backend/www-root/js/.gitignore
@@ -0,0 +1,3 @@
+dojo
+dojox
+dijit
diff --git a/My-Shop/go-backend/www-root/js/shop.js b/My-Shop/go-backend/www-root/js/shop.js
new file mode 100644
index 0000000..eebd7d4
--- /dev/null
+++ b/My-Shop/go-backend/www-root/js/shop.js
@@ -0,0 +1,217 @@
+var apibase = "/api/shop";
+var dialog;
+
+require([ "dojo/ready", "dojo/request/xhr", "dojo/dom-construct", "dojo/dom", "dojo/on", "dojo/dom-style", "dojo/dom-attr", "dijit/Dialog" ],
+ function(ready, xhr, domConstruct, dom, on, domStyle, domAttr, Dialog) {
+
+ function setOnCategoryClickHandler(node, catid, catname) {
+ on(node, "click", function() {
+ loadProducts(catid);
+ setSubTitle(catname);
+ });
+ }
+
+ function setOnProductClickHandler(node, id) {
+ on(node, "click", function() {
+ displayProduct(id);
+ });
+ }
+
+ function setSearchHandler() {
+ on(dom.byId("search_textbox"), "keyup", doSearch);
+ on(dom.byId("search_textbox"), "blur", function () {
+ console.log("SEARCH TEXT BOX >> Blur");
+ window.setInterval(function () { console.log("SEARCH TEXT BOX >> Je la cache"); domStyle.set("search_results", "visibility", "hidden"); }, 100);
+ });
+ on(dom.byId("search_textbox"), "focus", function () {
+ console.log("SEARCH TEXT BOX >> Focus");
+ var searchCriteria = domAttr.get("search_textbox", "value");
+ if (searchCriteria != "") {
+ domStyle.set("search_results", "visibility", "visible");
+ }
+ });
+ }
+
+ function setBuyHandler(node, id) {
+ on(node, "click", function() {
+ buyProduct(id);
+ });
+ }
+
+ function buyProduct(id) {
+ xhr(apibase + "/product/" + encodeURI(id) + "/buy",
+ { handleAs: "json" }
+ ).then(function (data) {
+ dialog.set("title", "Commande acceptée");
+ dialog.set("content", "La commande est partie. Vous allez très prochainement recevoir le produit.");
+ console.log("test...");
+ if (data.DownloadUrl != null && data.DownloadUrl != "") {
+ console.log("popup !");
+ window.location.href = data.DownloadUrl;
+ }
+ dialog.show();
+ displayProduct(id); // Refresh UI
+ }, function (err) {
+ if (err.response != null && err.response.status == 409) {
+ dialog.set("title", "Erreur");
+ dialog.set("content", "Le produit n'est plus en stock. Désolé.");
+ dialog.show();
+ } else {
+ dialog.set("title", "OOPS");
+ dialog.set("content", "Erreur interne. Désolé.");
+ dialog.show();
+ }
+ console.log(err);
+ displayProduct(id); // Refresh UI
+ }, function (evt) {
+
+ });
+ }
+
+ function doSearch(evt) {
+ var searchCriteria = domAttr.get("search_textbox", "value");
+ if (searchCriteria == "") {
+ domStyle.set("search_results", "visibility", "hidden");
+ return;
+ } else {
+ domStyle.set("search_results", "visibility", "visible");
+ }
+
+ xhr(apibase + "/search/" + encodeURI(searchCriteria),
+ { handleAs: "json" }
+ ).then(function (data) {
+ domConstruct.empty("search_results");
+ var placeholder = dom.byId("search_results");
+ for (var i = 0; i < data.length; i++) {
+ var div = domConstruct.create("div", {}, placeholder);
+ domConstruct.create("img", { width: "32px", src: "/img/" + data[i].Image }, div);
+ domConstruct.create("span", { textContent: data[i].Name }, div);
+ setOnProductClickHandler(div, data[i].Id);
+ }
+ if (data.length == 0) {
+ domConstruct.create("span", { textContent: "Aucun résultat", 'class': "no_result" }, placeholder);
+ }
+ }, function (err) {
+ console.log(err);
+ }, function (evt) {
+
+ });
+ }
+
+ function displayProduct(id) {
+ xhr(apibase + "/product/" + encodeURI(id),
+ { handleAs: "json" }
+ ).then(function (data) {
+ domConstruct.empty("products_pane");
+ var placeholder = dom.byId("products_pane");
+ var div = dojo.create("div", { 'class': 'product_detail' }, placeholder);
+ dojo.create("h1", { 'class': "product_name", textContent: data.Name }, div);
+ var div2 = dojo.create("div", {}, div)
+ dojo.create("img", { src: "/img/" + data.Image, height: "200px" }, div2);
+ dojo.create("span", { 'class': "product_price", textContent: data.Price + " €" }, div2);
+ if (data.Stock != "-1") {
+ dojo.create("span", { 'class': "product_stock", textContent: "En Stock: " + data.Stock }, div2);
+ }
+ if (data.VendorName != "") {
+ dojo.create("span", { 'class': "sold_by", textContent: "Vendu par: " + data.VendorName }, div2);
+ }
+
+ var buy_button = dojo.create("div", { 'class': "buy_button" }, div2);
+ dojo.create("div", { textContent: "Acheter !" }, buy_button);
+ setBuyHandler(buy_button, data.Id);
+
+ dojo.create("div", { 'class': "product_description", textContent: data.Description }, div);
+ }, function (err) {
+ if (err.response != null && err.response.status == 404) {
+ dialog.set("title", "Erreur");
+ dialog.set("content", "Le produit a été retiré de la vente. Désolé.");
+ dialog.show();
+ } else {
+ dialog.set("title", "OOPS");
+ dialog.set("content", "Erreur interne. Désolé.");
+ dialog.show();
+ }
+ console.log(err);
+ }, function (evt) {
+
+ });
+ }
+
+ function setSubTitle(name) {
+ domConstruct.empty("subtitle");
+ dom.byId("subtitle").textContent = name;
+ }
+
+ function loadProducts(catid) {
+ var queryString = "";
+ if (catid != null) {
+ queryString = "?category=" + encodeURI(catid);
+ }
+ xhr(apibase + "/product/" + queryString,
+ { handleAs: "json" }
+ ).then(function (data) {
+ domConstruct.empty("products_pane");
+ var placeholder = dom.byId("products_pane");
+ var table = domConstruct.create("table", { 'class': "product_table" }, placeholder);
+ var current_tr = null;
+ var i = 0;
+ for (; i < data.length; i++) {
+ if (i % 3 == 0) {
+ current_tr = domConstruct.create("tr", {}, table);
+ }
+ var td = domConstruct.create("td", {}, current_tr);
+ domConstruct.create("img", { width: "100px", src: "/img/" + data[i].Image }, td);
+ domConstruct.create("span", { textContent: data[i].Name }, td);
+ setOnProductClickHandler(td, data[i].Id);
+ }
+ // Fill remaining columns if less than 3 products
+ for (; i < 3; i++) {
+ domConstruct.create("td", {}, current_tr);
+ }
+ }, function (err) {
+ dialog.set("title", "OOPS");
+ dialog.set("content", "Erreur interne. Désolé.");
+ dialog.show();
+ console.log(err);
+ }, function (evt) {
+
+ });
+ }
+
+ function loadCategories() {
+ xhr(apibase + "/category/",
+ { handleAs: "json" }
+ ).then(function (data) {
+ var placeholder = dom.byId("category_list_placeholder");
+ domConstruct.empty("category_list_placeholder");
+ var all_products_node = domConstruct.create("span", { textContent: "Tous les produits", 'class': "category_item" }, placeholder);
+ setOnCategoryClickHandler(all_products_node, null, "Tous les produits");
+ for (var i = 0; i < data.length; i++) {
+ var catid = data[i].Id;
+ var catname = data[i].Name;
+ var node = domConstruct.create("span", { textContent: data[i].Name, 'class': "category_item" }, placeholder);
+ setOnCategoryClickHandler(node, catid, catname);
+ }
+ }, function (err) {
+ dialog.set("title", "OOPS");
+ dialog.set("content", "Erreur interne. Désolé.");
+ dialog.show();
+ console.log(err);
+ }, function (evt) {
+
+ });
+ }
+
+ ready(function() {
+ loadCategories();
+ loadProducts(null);
+ setSubTitle("Tous les produits");
+ setSearchHandler();
+ dialog = new Dialog({
+ id: "global_dialog",
+ title: "...",
+ content: "...",
+ style: "width: 500px; display: none;"
+ });
+ });
+});
diff --git a/My-Shop/go-backend/www-root/style.css b/My-Shop/go-backend/www-root/style.css
new file mode 100644
index 0000000..bb12160
--- /dev/null
+++ b/My-Shop/go-backend/www-root/style.css
@@ -0,0 +1,218 @@
+#search_bar {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ width: 450px;
+ height: 50px;
+}
+
+#search_textbox {
+ width: 350px;
+ height: 20px;
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ background-color: gray;
+}
+
+#search_results {
+ width: 350px;
+ position: absolute;
+ top: 30px;
+ right: 0px;
+ background-color: white;
+ border: 1px solid lightgrey;
+ z-index: 255;
+ visibility: hidden;
+}
+
+#search_results span, #search_results img {
+ display:inline-block;
+ vertical-align:middle;
+ margin-right: 5px;
+}
+
+#search_results div {
+ cursor: pointer; cursor: hand;
+ border: 1px solid white;
+}
+
+#search_results div:hover {
+ border: 1px solid gray;
+}
+
+.no_result {
+ color: lightgrey;
+ font-style: italic;
+}
+
+#category_bar {
+ position: absolute;
+ top: 100px;
+ left: 0px;
+ width: 200px;
+}
+
+#products_pane {
+ position: absolute;
+ top: 100px;
+ left: 250px;
+ right: 0px;
+ min-height: 300px;
+}
+
+#content_pane {
+ position: absolute;
+ top: 0px;
+ left: 30px;
+ right: 30px;
+}
+
+body {
+ background-color: #AAAAAA;
+ font-family: Sans-Serif;
+}
+
+#main_pane {
+ position: relative;
+ width: 80%;
+ margin: auto;
+ background-color: #EEEEEE;
+ min-height: 800px;
+}
+
+#site_header {
+ position: absolute;
+ top: 0px;
+ height: 116px;
+ left: 30px;
+ right: 30px;
+}
+
+#site_logo {
+ position: absolute;
+ top: 40px;
+ width: 345px;
+ left: 40px;
+}
+
+
+#pages_container {
+ position: absolute;
+ top: 140px;
+ bottom: 30px;
+ right: 30px;
+ left: 60px;
+}
+
+#page_title {
+ position: absolute;
+ top: 10px;
+ right: 30px;
+ left: 30px;
+ font: normal normal normal 25px/1.2em sans-serif;
+}
+
+#page_title > h2 {
+ line-height: 1.1em;
+ font: normal normal normal 22px/1.1em sans-serif;
+ color: #00CCFF;
+ margin : 0;
+ padding : 0;
+ border : 0;
+ outline : 0;
+}
+
+#page_title > h5 {
+ color: #7F7F7F;
+ line-height: 1.2em;
+ letter-spacing: normal;
+ margin : 0;
+ padding : 0;
+ border : 0;
+ outline : 0;
+}
+
+.product_table {
+ width: 100%;
+ border-spacing: 10px;
+ border-collapse: separate;
+}
+
+.product_table span, .product_table img {
+ display: block;
+ margin: auto;
+}
+
+.product_table img {
+ margin-bottom: 10px;
+}
+
+.product_table td {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ text-align: center;
+ background-color: white;
+ width: 33%;
+ color: darkgrey;
+ cursor: pointer; cursor: hand;
+}
+
+.category_item {
+ display: block;
+ margin-bottom: 10px;
+ text-decoration: none;
+ cursor: pointer; cursor: hand;
+}
+
+.category_item:hover {
+ text-decoration: underline;
+}
+
+.product_price {
+ position: absolute;
+ top: 20px;
+ left: 300px;
+ color: red;
+ font-size: x-large;
+}
+
+.product_detail > div {
+ position: relative;
+}
+
+.product_stock {
+ position: absolute;
+ top: 60px;
+ left: 300px;
+ color: darkgreen;
+}
+
+.sold_by {
+ position: absolute;
+ top: 80px;
+ left: 300px;
+ color: darkgreen;
+}
+
+.product_description {
+ margin-top: 20px;
+ color: darkgrey;
+}
+
+.buy_button {
+ position: absolute;
+ top: 150px;
+ left: 300px;
+ background-color: red;
+ color: white;
+ width: 100px;
+ height: 30px;
+ cursor: pointer; cursor: hand;
+}
+
+.buy_button > div {
+ text-align: center;
+ line-height: 30px;
+ vertical-align: middle;
+}
diff --git a/My-Shop/soap-backend/deploy.sh b/My-Shop/soap-backend/deploy.sh
new file mode 100755
index 0000000..05ad388
--- /dev/null
+++ b/My-Shop/soap-backend/deploy.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+(cd src && javac fr/itix/soapbackend/*.java)
+
+BASE=tomcat/webapps/axis2/WEB-INF/services/
+NAME=VendorBackend
+mkdir -p build/$NAME/META-INF build/$NAME/fr/itix/soapbackend/
+cp src/fr/itix/soapbackend/*.class build/$NAME/fr/itix/soapbackend/
+cp src/services.xml build/$NAME/META-INF/
+
+cp -rv build/$NAME $BASE/
+
+
diff --git a/My-Shop/soap-backend/src/fr/itix/soapbackend/.gitignore b/My-Shop/soap-backend/src/fr/itix/soapbackend/.gitignore
new file mode 100644
index 0000000..6b468b6
--- /dev/null
+++ b/My-Shop/soap-backend/src/fr/itix/soapbackend/.gitignore
@@ -0,0 +1 @@
+*.class
diff --git a/My-Shop/soap-backend/src/fr/itix/soapbackend/VendorBackend.java b/My-Shop/soap-backend/src/fr/itix/soapbackend/VendorBackend.java
new file mode 100644
index 0000000..3405c85
--- /dev/null
+++ b/My-Shop/soap-backend/src/fr/itix/soapbackend/VendorBackend.java
@@ -0,0 +1,14 @@
+package fr.itix.soapbackend;
+
+public class VendorBackend {
+ public String NotifySale(String productId, int number, String callerID) {
+ if ("12345".equals(productId) && "zouba-books".equals(callerID)) {
+ return "OK;http://online-shop.zouba-books.test:8787/book-1234.pdf";
+ }
+
+ if ("0001".equals(productId) && "mega-store".equals(callerID)) {
+ return "OK;";
+ }
+ throw new RuntimeException("Unknown caller id or wrong product id");
+ }
+}
diff --git a/My-Shop/soap-backend/src/services.xml b/My-Shop/soap-backend/src/services.xml
new file mode 100644
index 0000000..d6863cb
--- /dev/null
+++ b/My-Shop/soap-backend/src/services.xml
@@ -0,0 +1,16 @@
+
+
+ Vendor Backend
+
+
+
+
+
+
+ fr.itix.soapbackend.VendorBackend
+
+
diff --git a/WebAPI-Samples/LICENSE b/WebAPI-Samples/LICENSE
new file mode 100644
index 0000000..1885bc4
--- /dev/null
+++ b/WebAPI-Samples/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Nicolas MASSE
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/WebAPI-Samples/README.md b/WebAPI-Samples/README.md
new file mode 100644
index 0000000..56bf5d6
--- /dev/null
+++ b/WebAPI-Samples/README.md
@@ -0,0 +1,12 @@
+## WebAPI-Samples
+Sample Code for various Web API Protocols (SOAP, REST, etc.)
+
+### SOAP samples
+- [MTOM Attachments in Java](./SOAP/MTOM/)
+
+### REST samples
+- TODO
+
+### Utils
+- [A Reverse Proxy written in GO](./Utils/ReverseProxy/)
+
diff --git a/WebAPI-Samples/SOAP/MTOM/.gitignore b/WebAPI-Samples/SOAP/MTOM/.gitignore
new file mode 100644
index 0000000..a6387dd
--- /dev/null
+++ b/WebAPI-Samples/SOAP/MTOM/.gitignore
@@ -0,0 +1 @@
+tomcat
diff --git a/WebAPI-Samples/SOAP/MTOM/README.md b/WebAPI-Samples/SOAP/MTOM/README.md
new file mode 100644
index 0000000..562889c
--- /dev/null
+++ b/WebAPI-Samples/SOAP/MTOM/README.md
@@ -0,0 +1,29 @@
+# MTOM Sample Server Code
+
+## Introduction to MTOM
+
+MTOM is a standard to attach a file to a SOAP message (others way to do so are: MIME Attachments or in-line base64).
+
+For a good introduction to MTOM read :
+ - http://www.mkyong.com/webservices/jax-ws/jax-ws-attachment-with-mtom/
+ - https://axis.apache.org/axis2/java/core/docs/mtom-guide.html
+ - http://stackoverflow.com/questions/215741/how-does-mtom-work
+
+### How to know when a SOAP Message use MTOM ?
+
+The easy way :
+ - The HTTP request is a mime multipart
+```
+POST /path/to/ws HTTP/1.1
+Content-type: multipart/related; start="bla"; type="application/xop+xml"; boundary="uuid:bla...";
+```
+ - The SOAP Payload contains `Include` elements in the namespace `http://www.w3.org/2004/08/xop/include`
+```
+
+```
+
+## MTOM Server Code Setup
+
+See [the documentation](./doc/README.md).
+
diff --git a/WebAPI-Samples/SOAP/MTOM/build.sh b/WebAPI-Samples/SOAP/MTOM/build.sh
new file mode 100755
index 0000000..6a46096
--- /dev/null
+++ b/WebAPI-Samples/SOAP/MTOM/build.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+NAME=SoapBackend
+
+function die() {
+ echo "ERROR: $@"
+ exit 1
+}
+
+cd src || die "No source code"
+javac $(find . -iname *.java) || die "Compilation error"
+jar cvf ../build/$NAME.aar $(find . -iname *.class -or -iname *.xml) || die "Cannot build jar"
+
+
diff --git a/WebAPI-Samples/SOAP/MTOM/build/.gitignore b/WebAPI-Samples/SOAP/MTOM/build/.gitignore
new file mode 100644
index 0000000..6d81fcd
--- /dev/null
+++ b/WebAPI-Samples/SOAP/MTOM/build/.gitignore
@@ -0,0 +1 @@
+*.aar
diff --git a/WebAPI-Samples/SOAP/MTOM/doc/README.md b/WebAPI-Samples/SOAP/MTOM/doc/README.md
new file mode 100644
index 0000000..6008c28
--- /dev/null
+++ b/WebAPI-Samples/SOAP/MTOM/doc/README.md
@@ -0,0 +1,104 @@
+## MTOM Server Code Setup
+
+### Pre-requisites
+
+To make this sample code work, you need:
+ - Tomcat (tested against version 7)
+ - Axis2 (tested against version 1.6)
+
+Get tomcat7 and install it in a `tomcat` folder.
+```
+$ wget http://www.eu.apache.org/dist/tomcat/tomcat-7/v7.0.62/bin/apache-tomcat-7.0.62.tar.gz && tar zxvf apache-tomcat-*.tar.gz && mv apache-tomcat-*/ tomcat
+```
+
+Get axis2 and install the axis2.jar in `tomcat/webapps/axis2`
+
+```
+$ cd tomcat/webapps
+$ wget http://www.eu.apache.org/dist//axis/axis2/java/core/1.6.2/axis2-1.6.2-war.zip && unzip axis2-1.6.2-war.zip axis2.war
+$ cd ..
+$ ./bin/startup.sh
+```
+
+### Build
+
+Build the .aar archive and deploy it.
+
+
+
+### Change the Axis Configuration to enable MTOM
+
+Edit the Axis2 configuration file: `tomcat/webapps/axis2/WEB-INF/conf/axis2.xml` and set the `enableMTOM` parameter to `true`.
+
+
+
+**DO NOT FORGET TO RESTART TOMCAT !!!**
+
+## Test the MTOM attachment
+
+### Retrieve the WSDL
+
+Make sure Tomcat is started and the .aar archive is deployed. Then go to http://localhost:8080/axis2/services/listServices.
+
+Click on the `MTOMService` to get the WSDL.
+
+*Note:* make sure you save the file in its original format (File > Save As...). **Do not use copy paste that may break the XML format.**
+
+Just in case, you can find a copy of the WSDL [here](../wsdl/MtomService.wsdl).
+
+
+### Test in SOAP UI
+
+1. Get SOAP UI (tested with version 5)
+2. Import the WSDL
+3. Open the auto-generated request for the `MtomServiceSoap12Binding` binding
+ 
+4. Make sure `Enable MTOM` and `Force MTOM` in the bottom left pane are set to `true`.
+ 
+5. Import your attachment in the bottom pane.
+ 
+6. Make sure to reference the attachment in your SOAP message using `cid:attachment-name`.
+ 
+7. Make sure the attachement part in the bottom pane is set and the attachment type is set to `XOP`
+ 
+8. Fire the request and observe the result !
+ 
+
+Note: by clicking on the raw button in the request pane, you can have a look at the complete HTTP request.
+
+```
+POST http://localhost:8080/axis2/services/MtomService.MtomServiceHttpSoap12Endpoint/ HTTP/1.1
+Accept-Encoding: gzip,deflate
+Content-Type: multipart/related; type="application/xop+xml"; start=""; start-info="application/soap+xml"; action="urn:countBytes"; boundary="----=_Part_1_223849750.1433937520698"
+MIME-Version: 1.0
+Content-Length: 912
+Host: localhost:8080
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
+
+
+------=_Part_1_223849750.1433937520698
+Content-Type: application/xop+xml; charset=UTF-8; type="application/soap+xml"; action="countBytes"
+Content-Transfer-Encoding: 8bit
+Content-ID:
+
+
+
+
+
+
+
+
+
+
+------=_Part_1_223849750.1433937520698
+Content-Type: text/plain; charset=us-ascii; name=my-attachment.txt
+Content-Transfer-Encoding: 7bit
+Content-ID:
+Content-Disposition: attachment; name="my-attachment.txt"; filename="my-attachment.txt"
+
+Hello World !
+
+------=_Part_1_223849750.1433937520698--
+```
+
diff --git a/WebAPI-Samples/SOAP/MTOM/doc/attach-soapui.png b/WebAPI-Samples/SOAP/MTOM/doc/attach-soapui.png
new file mode 100644
index 0000000..fd56e93
Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/attach-soapui.png differ
diff --git a/WebAPI-Samples/SOAP/MTOM/doc/axis-services.png b/WebAPI-Samples/SOAP/MTOM/doc/axis-services.png
new file mode 100644
index 0000000..92db5d8
Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/axis-services.png differ
diff --git a/WebAPI-Samples/SOAP/MTOM/doc/binding.png b/WebAPI-Samples/SOAP/MTOM/doc/binding.png
new file mode 100644
index 0000000..fa0af7a
Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/binding.png differ
diff --git a/WebAPI-Samples/SOAP/MTOM/doc/build.png b/WebAPI-Samples/SOAP/MTOM/doc/build.png
new file mode 100644
index 0000000..9269968
Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/build.png differ
diff --git a/WebAPI-Samples/SOAP/MTOM/doc/enable-mtom.png b/WebAPI-Samples/SOAP/MTOM/doc/enable-mtom.png
new file mode 100644
index 0000000..2fe218b
Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/enable-mtom.png differ
diff --git a/WebAPI-Samples/SOAP/MTOM/doc/mtom-soapui.png b/WebAPI-Samples/SOAP/MTOM/doc/mtom-soapui.png
new file mode 100644
index 0000000..4aee2d5
Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/mtom-soapui.png differ
diff --git a/WebAPI-Samples/SOAP/MTOM/doc/soap-result.png b/WebAPI-Samples/SOAP/MTOM/doc/soap-result.png
new file mode 100644
index 0000000..78276d2
Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/soap-result.png differ
diff --git a/WebAPI-Samples/SOAP/MTOM/doc/soap-soapui.png b/WebAPI-Samples/SOAP/MTOM/doc/soap-soapui.png
new file mode 100644
index 0000000..50f946e
Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/soap-soapui.png differ
diff --git a/WebAPI-Samples/SOAP/MTOM/src/META-INF/services.xml b/WebAPI-Samples/SOAP/MTOM/src/META-INF/services.xml
new file mode 100644
index 0000000..cab236d
--- /dev/null
+++ b/WebAPI-Samples/SOAP/MTOM/src/META-INF/services.xml
@@ -0,0 +1,16 @@
+
+
+ SOAP Backend that exposes an MTOM enabled service
+
+
+
+
+
+
+ fr.itix.soapbackend.MTOMService
+
+
diff --git a/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/.gitignore b/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/.gitignore
new file mode 100644
index 0000000..6b468b6
--- /dev/null
+++ b/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/.gitignore
@@ -0,0 +1 @@
+*.class
diff --git a/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/MTOMService.java b/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/MTOMService.java
new file mode 100644
index 0000000..5ea0378
--- /dev/null
+++ b/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/MTOMService.java
@@ -0,0 +1,16 @@
+package fr.itix.soapbackend;
+
+import javax.jws.WebMethod;
+import javax.jws.WebService;
+import javax.xml.ws.soap.MTOM;
+
+@MTOM
+@WebService(name="MtomPortType",
+ serviceName="MtomService",
+ targetNamespace="http://itix.fr/soap/mtom")
+public class MTOMService {
+ @WebMethod
+ public int countBytes(byte[] bytes) {
+ return bytes.length;
+ }
+}
diff --git a/WebAPI-Samples/SOAP/MTOM/wsdl/MtomService.wsdl b/WebAPI-Samples/SOAP/MTOM/wsdl/MtomService.wsdl
new file mode 100644
index 0000000..ccc8588
--- /dev/null
+++ b/WebAPI-Samples/SOAP/MTOM/wsdl/MtomService.wsdl
@@ -0,0 +1,81 @@
+
+
+ VendorBackend
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebAPI-Samples/SOAP/MTOM/wsdl/README.md b/WebAPI-Samples/SOAP/MTOM/wsdl/README.md
new file mode 100644
index 0000000..bd21348
--- /dev/null
+++ b/WebAPI-Samples/SOAP/MTOM/wsdl/README.md
@@ -0,0 +1 @@
+This a sample WSDL, generated by Axis
diff --git a/WebAPI-Samples/Utils/ReverseProxy/README.md b/WebAPI-Samples/Utils/ReverseProxy/README.md
new file mode 100644
index 0000000..e634d9f
--- /dev/null
+++ b/WebAPI-Samples/Utils/ReverseProxy/README.md
@@ -0,0 +1,27 @@
+## Reverse Proxy written in GO
+This reverse proxy listens on a local port and forward all requests to a named host. It honors the proxy environment variables.
+
+### Initial Need
+
+Once upon a time, I had to circumvent a bug in a product that could not handle correctly an HTTPS connection to a proxy.
+
+Since it was an HTTPS connection, I could not setup a transparent proxy.
+
+### What it does
+
+It opens a local port and listen to HTTP requests, forwards the requests to a named host and send back the response.
+
+### How to use it
+
+```bash
+go run src/itix.fr/forward/main.go -local-port 8080 -target https://www.opentrust.com
+curl -D - http://localhost:8080/robots.txt
+```
+
+If you want to go through a proxy, do not forget to set the ```http_proxy``` and ```https_proxy``` variables !
+
+```bash
+export http_proxy=http://my.proxy:8888/
+export https_proxy=http://my.proxy:8888/
+```
+
diff --git a/WebAPI-Samples/Utils/ReverseProxy/set-go-path.sh b/WebAPI-Samples/Utils/ReverseProxy/set-go-path.sh
new file mode 100644
index 0000000..0c57277
--- /dev/null
+++ b/WebAPI-Samples/Utils/ReverseProxy/set-go-path.sh
@@ -0,0 +1,3 @@
+export GOPATH="$PWD"
+echo "GOPATH=$GOPATH"
+
diff --git a/WebAPI-Samples/Utils/ReverseProxy/src/itix.fr/forward/main.go b/WebAPI-Samples/Utils/ReverseProxy/src/itix.fr/forward/main.go
new file mode 100644
index 0000000..a2e7056
--- /dev/null
+++ b/WebAPI-Samples/Utils/ReverseProxy/src/itix.fr/forward/main.go
@@ -0,0 +1,96 @@
+package main
+
+import (
+ "os"
+ "fmt"
+ "flag"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+)
+
+// The local port on which we should listen to
+var local_port int
+
+// The target URL on which we should redirect requests
+var target string
+
+func init() {
+ // --local-port=9009
+ flag.IntVar(&local_port, "local-port", 9090, "the TCP port to listen to")
+
+ // --target=http://www.perdu.com
+ flag.StringVar(&target, "target", "http://www.perdu.com", "the target URL to redirect the request to")
+}
+
+// The MyResponseWriter is a wrapper around the standard http.ResponseWriter
+// We need it to retrieve the http status code of an http response
+type MyResponseWriter struct {
+ Underlying http.ResponseWriter
+ Status int
+}
+
+func (mrw *MyResponseWriter) Header() http.Header {
+ return mrw.Underlying.Header()
+}
+
+func (mrw *MyResponseWriter) Write(b []byte) (int, error) {
+ return mrw.Underlying.Write(b)
+}
+
+func (mrw *MyResponseWriter) WriteHeader(s int) {
+ mrw.Status = s
+ mrw.Underlying.WriteHeader(s)
+}
+
+func main() {
+ // Parse the command line arguments
+ flag.Parse()
+
+ // Parse the target URL and perform some sanity checks
+ url, err := url.Parse(target)
+ if err != nil {
+ panic(err)
+ }
+
+ // Initialize a Reverse Proxy object with a custom director
+ proxy := httputil.NewSingleHostReverseProxy(url)
+ underlying_director := proxy.Director
+ proxy.Director = func(req *http.Request) {
+ // Let the underlying director do the mandatory job
+ underlying_director(req)
+
+ // Custom Handling
+ // ---------------
+ //
+ // Filter out the "Host" header sent by the client
+ // otherwise the target server won't be able to find the
+ // matching virtual host. The correct host header will be
+ // added automatically by the net/http package.
+ req.Host = ""
+ }
+
+ http.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
+ // Log the incoming request (including headers)
+ fmt.Printf("%v %v HTTP/1.1\n", req.Method, req.URL)
+ req.Header.Write(os.Stdout)
+ fmt.Println()
+
+ // Wrap the standard response writer with our own
+ // implementation because we need the status code of the
+ // response and that field is not exported by default
+ mrw := &MyResponseWriter{ Underlying: rw }
+
+ // Let the reverse proxy handle the request
+ proxy.ServeHTTP(mrw, req)
+
+ // Log the response
+ fmt.Printf("%v %v\n", mrw.Status, http.StatusText(mrw.Status))
+ mrw.Header().Write(os.Stdout)
+ fmt.Println()
+ })
+
+ fmt.Printf("Listening on port %v for incoming requests...\n", local_port)
+ http.ListenAndServe(fmt.Sprintf(":%v", local_port), nil)
+}
+