Commit 2ea67927f5e97161f0dfc7d0b7c2c33aff370d2a

Authored by Marko Tikvić
1 parent 6620591d86
Exists in master and in 1 other branch v2

added support for metadata update

Showing 2 changed files with 214 additions and 84 deletions   Show diff stats
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "encoding/json" 4 "encoding/json"
5 "net/http" 5 "net/http"
6 ) 6 )
7 7
8 const ( 8 const (
9 templateHttpErr500_EN = "An internal server error has occurred." 9 templateHttpErr500_EN = "An internal server error has occurred."
10 templateHttpErr500_RS = "Došlo je do greške na serveru." 10 templateHttpErr500_RS = "Došlo je do greške na serveru."
11 templateHttpErr400_EN = "Bad request: invalid request body." 11 templateHttpErr400_EN = "Bad request."
12 templateHttpErr400_RS = "Neispravan zahtev." 12 templateHttpErr400_RS = "Neispravan zahtev."
13 templateHttpErr404_EN = "Resource not found." 13 templateHttpErr404_EN = "Resource not found."
14 templateHttpErr404_RS = "Objekat nije pronadjen." 14 templateHttpErr404_RS = "Objekat nije pronadjen."
15 templateHttpErr401_EN = "Unauthorized request." 15 templateHttpErr401_EN = "Unauthorized request."
16 templateHttpErr401_RS = "Neautorizovan zahtev." 16 templateHttpErr401_RS = "Neautorizovan zahtev."
17 ) 17 )
18 18
19 type httpError struct { 19 type httpError struct {
20 Error []HttpErrorDesc `json:"error"` 20 Error []HttpErrorDesc `json:"error"`
21 Request string `json:"request"` 21 Request string `json:"request"`
22 } 22 }
23 23
24 type HttpErrorDesc struct { 24 type HttpErrorDesc struct {
25 Lang string `json:"lang"` 25 Lang string `json:"lang"`
26 Desc string `json:"description"` 26 Desc string `json:"description"`
27 } 27 }
28 28
29 // DeliverPayload encodes payload as JSON to w. 29 // DeliverPayload encodes payload as JSON to w.
30 func DeliverPayload(w http.ResponseWriter, payload Payload) { 30 func DeliverPayload(w http.ResponseWriter, payload Payload) {
31 // Don't write status OK in the headers here. Leave it up for the caller. 31 // Don't write status OK in the headers here. Leave it up for the caller.
32 // E.g. Status 201. 32 // E.g. Status 201.
33 json.NewEncoder(w).Encode(payload) 33 json.NewEncoder(w).Encode(payload)
34 payload.Data = nil 34 payload.Data = nil
35 } 35 }
36 36
37 // ErrorResponse writes HTTP error to w. 37 // ErrorResponse writes HTTP error to w.
38 func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { 38 func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) {
39 err := httpError{desc, r.Method + " " + r.RequestURI} 39 err := httpError{desc, r.Method + " " + r.RequestURI}
40 w.WriteHeader(code) 40 w.WriteHeader(code)
41 json.NewEncoder(w).Encode(err) 41 json.NewEncoder(w).Encode(err)
42 } 42 }
43 43
44 // NotFoundResponse writes HTTP error 404 to w. 44 // NotFoundResponse writes HTTP error 404 to w.
45 func NotFoundResponse(w http.ResponseWriter, req *http.Request) { 45 func NotFoundResponse(w http.ResponseWriter, req *http.Request) {
46 ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ 46 ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{
47 {"en", templateHttpErr404_EN}, 47 {"en", templateHttpErr404_EN},
48 {"rs", templateHttpErr404_RS}, 48 {"rs", templateHttpErr404_RS},
49 }) 49 })
50 } 50 }
51 51
52 // BadRequestResponse writes HTTP error 400 to w. 52 // BadRequestResponse writes HTTP error 400 to w.
53 func BadRequestResponse(w http.ResponseWriter, req *http.Request) { 53 func BadRequestResponse(w http.ResponseWriter, req *http.Request) {
54 ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ 54 ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{
55 {"en", templateHttpErr400_EN}, 55 {"en", templateHttpErr400_EN},
56 {"rs", templateHttpErr400_RS}, 56 {"rs", templateHttpErr400_RS},
57 }) 57 })
58 } 58 }
59 59
60 // InternalSeverErrorResponse writes HTTP error 500 to w. 60 // InternalSeverErrorResponse writes HTTP error 500 to w.
61 func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { 61 func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) {
62 ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ 62 ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{
63 {"en", templateHttpErr500_EN}, 63 {"en", templateHttpErr500_EN},
64 {"rs", templateHttpErr500_RS}, 64 {"rs", templateHttpErr500_RS},
65 }) 65 })
66 } 66 }
67 67
68 // UnauthorizedError writes HTTP error 401 to w. 68 // UnauthorizedError writes HTTP error 401 to w.
69 func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { 69 func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) {
70 w.Header().Set("WWW-Authenticate", "Bearer") 70 w.Header().Set("WWW-Authenticate", "Bearer")
71 ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ 71 ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{
72 {"en", templateHttpErr401_EN}, 72 {"en", templateHttpErr401_EN},
73 {"rs", templateHttpErr401_RS}, 73 {"rs", templateHttpErr401_RS},
74 }) 74 })
75 } 75 }
76 76
77 // NotFoundHandler writes HTTP error 404 to w. 77 // NotFoundHandler writes HTTP error 404 to w.
78 func NotFoundHandler(w http.ResponseWriter, req *http.Request) { 78 func NotFoundHandler(w http.ResponseWriter, req *http.Request) {
79 SetDefaultHeaders(w) 79 SetDefaultHeaders(w)
80 if req.Method == "OPTIONS" { 80 if req.Method == "OPTIONS" {
81 return 81 return
82 } 82 }
83 ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ 83 ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{
84 {"en", "Not found."}, 84 {"en", "Not found."},
85 {"rs", "Traženi resurs ne postoji."}, 85 {"rs", "Traženi resurs ne postoji."},
86 }) 86 })
87 } 87 }
88 88
89 // SetDefaultHeaders set's default headers for an HTTP response. 89 // SetDefaultHeaders set's default headers for an HTTP response.
90 func SetDefaultHeaders(w http.ResponseWriter) { 90 func SetDefaultHeaders(w http.ResponseWriter) {
91 w.Header().Set("Access-Control-Allow-Origin", "*") 91 w.Header().Set("Access-Control-Allow-Origin", "*")
92 w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") 92 w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
93 w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, 93 w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type,
94 Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) 94 Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`)
95 w.Header().Set("Content-Type", "application/json; charset=utf-8") 95 w.Header().Set("Content-Type", "application/json; charset=utf-8")
96 } 96 }
97 97
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "encoding/json" 4 "encoding/json"
5 "errors" 5 "errors"
6 "fmt" 6 "fmt"
7 "io" 7 "io"
8 "net/http" 8 "net/http"
9 "os"
9 "sync" 10 "sync"
11 "time"
10 12
11 "gopkg.in/rana/ora.v4" 13 "gopkg.in/rana/ora.v4"
12 ) 14 )
13 15
14 var mu = &sync.Mutex{} 16 var (
15 var payloads []Payload 17 mu = &sync.Mutex{}
18 metadata map[string]Payload
19
20 metadataDB *ora.Ses
21 activeProject string
22
23 inited bool
24 )
16 25
17 type LangMap map[string]map[string]string 26 type LangMap map[string]map[string]string
18 27
19 type Field struct { 28 type Field struct {
20 Parameter string `json:"param"` 29 Parameter string `json:"param"`
21 Type string `json:"type"` 30 Type string `json:"type"`
22 Visible bool `json:"visible"` 31 Visible bool `json:"visible"`
23 Editable bool `json:"editable"` 32 Editable bool `json:"editable"`
24 } 33 }
25 34
26 type CorrelationField struct { 35 type CorrelationField struct {
27 Result string `json:"result"` 36 Result string `json:"result"`
28 Elements []string `json:"elements"` 37 Elements []string `json:"elements"`
29 Type string `json:"type"` 38 Type string `json:"type"`
30 } 39 }
31 40
32 type Translation struct { 41 type Translation struct {
33 Language string `json:"language"` 42 Language string `json:"language"`
34 FieldsLabels map[string]string `json:"fieldsLabels"` 43 FieldsLabels map[string]string `json:"fieldsLabels"`
35 } 44 }
36 45
37 type Payload struct { 46 type Payload struct {
38 Type string `json:"type"`
39 Method string `json:"method"` 47 Method string `json:"method"`
40 Params map[string]string `json:"params"` 48 Params map[string]string `json:"params"`
41 Lang []Translation `json:"lang"` 49 Lang []Translation `json:"lang"`
42 Fields []Field `json:"fields"` 50 Fields []Field `json:"fields"`
43 Correlations []CorrelationField `json:"correlationFields"` 51 Correlations []CorrelationField `json:"correlationFields"`
44 IdField string `json:"idField"` 52 IdField string `json:"idField"`
45 53
46 // Data holds JSON payload. It can't be used for itteration. 54 // Data holds JSON payload. It can't be used for itteration.
47 Data interface{} `json:"data"` 55 Data interface{} `json:"data"`
48 } 56 }
49 57
50 // InitPayloadsMetaData loads all payloads in the payloads variable. 58 // LoadPayloadsdetaData loads all payloads' information into 'metadata' variable.
51 // Returns an error if it fails. 59 func LoadPayloadsMetadata(db *ora.Ses, project string, hotloading bool, hlPeriod int) error {
52 func InitPayloadsMetaData(db *ora.Ses, project string) error { 60 metadataDB = db
53 payloads = make([]Payload, 0) 61 activeProject = project
54 62
55 jsonbuf, err := fetchJSON(db, project) 63 mu.Lock()
64 defer mu.Unlock()
65 err := initMetadata(project)
56 if err != nil { 66 if err != nil {
57 return err 67 return err
58 } 68 }
69 if hotloading {
70 go hotload(hlPeriod)
71 }
72 inited = true
73
74 return nil
75 }
76
77 func UpdateMetadataModels(md map[string][]byte) (upd, add int, err error) {
78 if !inited {
79 return 0, 0, errors.New("webutil: metadata not initialized but update was tried.")
80 }
81
82 forUpdate := make([]string, 0)
83 forCreate := make([]string, 0)
84
85 for k, _ := range md {
86 if _, exists := metadata[k]; exists {
87 forUpdate = append(forUpdate, k)
88 } else {
89 forCreate = append(forCreate, k)
90 }
91 }
92
93 for _, k := range forUpdate {
94 fmt.Printf("for update: %s\n", k)
95 _, err := metadataDB.PrepAndExe(`update entities set
96 entity_model = :1
97 where entity_type = :2`,
98 string(md[k]),
99 k)
100
101 if err != nil {
102 fmt.Printf("webutility: update metadata: prep and exe: %v\n", err)
103 continue
104 }
105 upd++
106 }
107
108 for _, k := range forCreate {
109 fmt.Printf("for add: %s\n", k)
110 /*
111 _, err := metadataDB.PrepAndExe(`insert into entities
112 (projekat, metadata, entity_type, entity_model)
113 values(:1, :2, :3, :4)`,
114 activeProject, "", k, string(md[k]))
115
116 if err != nil {
117 fmt.Printf("webutility: add metadata: prep and exe: %v\n", err)
118 continue
119 }
120 */
121 add++
122 }
123
124 return upd, add, nil
125 }
126
127 func GetMetadataForAllEntities() map[string]Payload {
128 return metadata
129 }
130
131 func GetMetadataForEntityType(t string) Payload {
132 return metadata[t]
133 }
59 134
135 func UpdateMetadata(entityType string, p *Payload) error {
136 md, err := json.Marshal(p)
137 if err != nil {
138 return err
139 }
140 fmt.Printf("md: %s %s\n", entityType, string(md))
60 mu.Lock() 141 mu.Lock()
61 defer mu.Unlock() 142 defer mu.Unlock()
62 json.Unmarshal(jsonbuf, &payloads) 143 _, err = metadataDB.PrepAndExe(`update entities set
63 if len(payloads) == 0 { 144 metadata = :1
64 return errors.New("tables config is corrupt") 145 where projekat = :2
146 and entity_type = :3`,
147 string(md),
148 activeProject,
149 entityType)
150 if err != nil {
151 return err
65 } 152 }
66 return nil 153 return nil
67 } 154 }
68 155
69 // DecodeJSON decodes JSON data from r to v. 156 // DecodeJSON decodes JSON data from r to v.
70 // Returns an error if it fails. 157 // Returns an error if it fails.
71 func DecodeJSON(r io.Reader, v interface{}) error { 158 func DecodeJSON(r io.Reader, v interface{}) error {
72 return json.NewDecoder(r).Decode(v) 159 return json.NewDecoder(r).Decode(v)
73 } 160 }
74 161
75 // NewPayload returs a payload sceleton for provided table. 162 // NewPayload returs a payload sceleton for entity described with etype.
76 func NewPayload(r *http.Request, table string) Payload { 163 func NewPayload(r *http.Request, etype string) Payload {
77 var pload Payload 164 pload := metadata[etype]
78
79 pload.Method = r.Method + " " + r.RequestURI 165 pload.Method = r.Method + " " + r.RequestURI
80 pload.Type = table
81 if table != "" {
82 pload.Params = make(map[string]string, 0)
83 pload.Lang = translations(table)
84 pload.Fields = fields(table)
85 pload.IdField = id(table)
86 pload.Correlations = correlations(table)
87 }
88 return pload 166 return pload
89 } 167 }
90 168
91 // translations returns a slice of translations for a payload/table of ptype type. 169 func initMetadata(project string) error {
92 func translations(ptype string) []Translation { 170 metadataDB.SetCfg(metadataDB.Cfg().SetClob(ora.S))
93 var translations []Translation 171 stmt, err := metadataDB.Prep(`select
94 172 entity_type,
95 for _, pload := range payloads { 173 metadata
96 if pload.Type == ptype { 174 from entities
97 for _, t := range pload.Lang { 175 where projekat = `+fmt.Sprintf("'%s'", project),
98 translations = append(translations, Translation{ 176 ora.S,
99 Language: t.Language, 177 ora.S)
100 FieldsLabels: t.FieldsLabels, 178
101 }) 179 defer stmt.Close()
102 } 180 if err != nil {
103 } 181 return err
104 } 182 }
105 183
106 return translations 184 rset, err := stmt.Qry()
107 } 185 if err != nil {
186 return err
187 }
108 188
109 // fields returns a slice of fields for a payload/table of ptype type. 189 count := 0
110 func fields(ptype string) []Field { 190 success := 0
111 var fields []Field 191 metadata = make(map[string]Payload)
192 for rset.Next() {
193 name := rset.Row[0].(string)
194 load := []byte(rset.Row[1].(string))
112 195
113 for _, pload := range payloads { 196 p := Payload{}
114 if pload.Type == ptype { 197 err := json.Unmarshal(load, &p)
115 for _, f := range pload.Fields { 198 if err != nil {
116 fields = append(fields, f) 199 fmt.Printf("couldn't init: '%s' metadata\n", name)
117 } 200 } else {
201 success++
202 metadata[name] = p
118 } 203 }
204 count++
119 } 205 }
206 fmt.Printf("webutility: successfully loaded %d/%d (%.1f%%) entities\n",
207 success, count, float32(success)/float32(count)*100.0)
120 208
121 return fields 209 return nil
122 } 210 }
123 211
124 // id returns the name of ID field of a payload/table of ptype type. 212 func hotload(n int) {
125 func id(ptype string) string { 213 entityScan := make(map[string]int64)
126 for _, pload := range payloads { 214 firstCheck := true
127 if pload.Type == ptype { 215 for {
128 return pload.IdField 216 time.Sleep(time.Duration(n) * time.Second)
217 stmt, err := metadataDB.Prep(`select
218 ora_rowscn,
219 entity_type
220 from entities where projekat = `+fmt.Sprintf("'%s'", activeProject),
221 ora.I64,
222 ora.S)
223 if err != nil {
224 fmt.Fprintf(os.Stderr, "hotload failed: %v\n", err)
225 time.Sleep(time.Duration(n) * time.Second)
226 continue
129 } 227 }
130 }
131 return ""
132 }
133 228
134 // correlations returns a slice of correlation fields for a payload/table of ptype type. 229 rset, err := stmt.Qry()
135 func correlations(ptype string) []CorrelationField { 230 if err != nil {
136 var corr []CorrelationField 231 fmt.Fprintf(os.Stderr, "hotload failed: %v\n", err)
232 time.Sleep(time.Duration(n) * time.Second)
233 continue
234 }
137 235
138 for _, pload := range payloads { 236 var toRefresh []string
139 if pload.Type == ptype { 237 for rset.Next() {
140 for _, c := range pload.Correlations { 238 scanID := rset.Row[0].(int64)
141 corr = append(corr, c) 239 entity := rset.Row[1].(string)
240 oldID, ok := entityScan[entity]
241 if !ok || oldID != scanID {
242 entityScan[entity] = scanID
243 toRefresh = append(toRefresh, entity)
142 } 244 }
143 } 245 }
144 } 246 stmt.Close()
145 247
146 return corr 248 if rset.Err() != nil {
147 } 249 fmt.Fprintf(os.Stderr, "hotload rset error: %v\n", rset.Err())
250 time.Sleep(time.Duration(n) * time.Second)
251 continue
252 }
148 253
149 // fetchJSON returns a byte slice of JSON configuration file from TABLES_CONFIG table. 254 if len(toRefresh) > 0 && !firstCheck {
150 // Returns an error if it fails. 255 mu.Lock()
151 func fetchJSON(db *ora.Ses, project string) ([]byte, error) { 256 refreshMetadata(toRefresh)
152 db.SetCfg(db.Cfg().SetClob(ora.S)) 257 mu.Unlock()
153 stmt, err := db.Prep(`SELECT JSON_NCLOB FROM TABLES_CONFIG WHERE PROJEKAT = `+fmt.Sprintf("'%s'", project), ora.S) 258 }
154 defer stmt.Close() 259 if firstCheck {
155 if err != nil { 260 firstCheck = false
156 return nil, err 261 }
157 } 262 }
263 }
158 264
159 rset, err := stmt.Qry() 265 func refreshMetadata(entities []string) {
160 if err != nil { 266 for _, e := range entities {
161 return nil, err 267 //fmt.Printf("refreshing %s\n", e)
162 } 268 stmt, err := metadataDB.Prep(`select
269 metadata
270 from entities
271 where projekat = `+fmt.Sprintf("'%s'", activeProject)+
272 ` and entity_type = `+fmt.Sprintf("'%s'", e),
273 ora.S)
163 274
164 var data string 275 if err != nil {
165 if rset.Next() { 276 fmt.Printf("webutility: refresh: prep: %v\n", err)
166 data = rset.Row[0].(string) 277 stmt.Close()
167 } 278 continue
279 }
168 280
169 //fmt.Println(data) 281 rset, err := stmt.Qry()
170 return []byte(data), nil 282 if err != nil {
283 fmt.Printf("webutility: refresh: query: %v\n", err)
284 stmt.Close()
285 continue
286 }
287
288 for rset.Next() {
289 load := []byte(rset.Row[0].(string))