Commit 8a070abe23ba5bc8149d707a10744ff18f42c8a8

Authored by Marko Tikvić
1 parent 44c82dbac6
Exists in master

improved

Showing 1 changed file with 45 additions and 20 deletions   Show diff stats
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "database/sql" 4 "database/sql"
5 "encoding/json" 5 "encoding/json"
6 "errors" 6 "errors"
7 "fmt" 7 "fmt"
8 "io" 8 "io"
9 "net/http" 9 "net/http"
10 "os" 10 "os"
11 "strings" 11 "strings"
12 "sync" 12 "sync"
13 "time" 13 "time"
14 14
15 "git.to-net.rs/marko.tikvic/gologger" 15 "git.to-net.rs/marko.tikvic/gologger"
16 ) 16 )
17 17
18 var ( 18 var (
19 mu = &sync.Mutex{} 19 mu = &sync.Mutex{}
20 metadata = make(map[string]Payload) 20 metadata = make(map[string]Payload)
21 21
22 updateQue = make(map[string][]byte) 22 updateQue = make(map[string][]byte)
23 23
24 metadataDB *sql.DB 24 metadataDB *sql.DB
25 activeProject string 25 activeProject string
26 26
27 inited bool 27 inited bool
28 driver string 28 driver string
29 logger *gologger.Logger 29 logger *gologger.Logger
30 ) 30 )
31 31
32 type LangMap map[string]map[string]string 32 type LangMap map[string]map[string]string
33 33
34 type Field struct { 34 type Field struct {
35 Parameter string `json:"param"` 35 Parameter string `json:"param"`
36 Type string `json:"type"` 36 Type string `json:"type"`
37 Visible bool `json:"visible"` 37 Visible bool `json:"visible"`
38 Editable bool `json:"editable"` 38 Editable bool `json:"editable"`
39 } 39 }
40 40
41 type CorrelationField struct { 41 type CorrelationField struct {
42 Result string `json:"result"` 42 Result string `json:"result"`
43 Elements []string `json:"elements"` 43 Elements []string `json:"elements"`
44 Type string `json:"type"` 44 Type string `json:"type"`
45 } 45 }
46 46
47 type Translation struct { 47 type Translation struct {
48 Language string `json:"language"` 48 Language string `json:"language"`
49 FieldsLabels map[string]string `json:"fieldsLabels"` 49 FieldsLabels map[string]string `json:"fieldsLabels"`
50 } 50 }
51 51
52 // output 52 // output
53 type PaginationLinks struct { 53 type PaginationLinks struct {
54 Base string `json:"base"` 54 Base string `json:"base"`
55 Next string `json:"next"` 55 Next string `json:"next"`
56 Prev string `json:"prev"` 56 Prev string `json:"prev"`
57 Self string `json:"self"` 57 Self string `json:"self"`
58 } 58 }
59 59
60 // input 60 // input
61 type PaginationParameters struct { 61 type PaginationParameters struct {
62 URL string `json:"-"` 62 URL string `json:"-"`
63 Offset int64 `json:"offset"` 63 Offset int64 `json:"offset"`
64 Limit int64 `json:"limit"` 64 Limit int64 `json:"limit"`
65 SortBy string `json:"sortBy"` 65 SortBy string `json:"sortBy"`
66 Order string `json:"order"` 66 Order string `json:"order"`
67 } 67 }
68 68
69 // TODO(marko) 69 // TODO(marko)
70 func GetPaginationParameters(req *http.Request) (p PaginationParameters) { 70 func GetPaginationParameters(req *http.Request) (p PaginationParameters) {
71 return p 71 return p
72 } 72 }
73 73
74 // TODO(marko) 74 // TODO(marko)
75 func (p *PaginationParameters) paginationLinks() (links PaginationLinks) { 75 func (p *PaginationParameters) paginationLinks() (links PaginationLinks) {
76 return links 76 return links
77 } 77 }
78 78
79 type Payload struct { 79 type Payload struct {
80 Method string `json:"method"` 80 Method string `json:"method"`
81 Params map[string]string `json:"params"` 81 Params map[string]string `json:"params"`
82 Lang []Translation `json:"lang"` 82 Lang []Translation `json:"lang"`
83 Fields []Field `json:"fields"` 83 Fields []Field `json:"fields"`
84 Correlations []CorrelationField `json:"correlationFields"` 84 Correlations []CorrelationField `json:"correlationFields"`
85 IdField string `json:"idField"` 85 IdField string `json:"idField"`
86 86
87 // Pagination 87 // Pagination
88 Count int64 `json:"count"` 88 Count int64 `json:"count"`
89 Total int64 `json:"total"` 89 Total int64 `json:"total"`
90 Links PaginationLinks `json:"_links"` 90 Links PaginationLinks `json:"_links"`
91 91
92 // Data holds JSON payload. It can't be used for itteration. 92 // Data holds JSON payload. It can't be used for itteration.
93 Data interface{} `json:"data"` 93 Data interface{} `json:"data"`
94 } 94 }
95 95
96 func (p *Payload) addLang(code string, labels map[string]string) {
97 t := Translation{
98 Language: code,
99 FieldsLabels: labels,
100 }
101 p.Lang = append(p.Lang, t)
102 }
103
96 func (p *Payload) SetData(data interface{}) { 104 func (p *Payload) SetData(data interface{}) {
97 p.Data = data 105 p.Data = data
98 } 106 }
99 107
100 func (p *Payload) SetPaginationInfo(count, total int64, params PaginationParameters) { 108 func (p *Payload) SetPaginationInfo(count, total int64, params PaginationParameters) {
101 p.Count = count 109 p.Count = count
102 p.Total = total 110 p.Total = total
103 p.Links = params.paginationLinks() 111 p.Links = params.paginationLinks()
104 } 112 }
105 113
106 // NewPayload returs a payload sceleton for entity described with key. 114 // NewPayload returs a payload sceleton for entity described with key.
107 func NewPayload(r *http.Request, key string) Payload { 115 func NewPayload(r *http.Request, key string) Payload {
108 p := metadata[key] 116 p := metadata[key]
109 p.Method = r.Method + " " + r.RequestURI 117 p.Method = r.Method + " " + r.RequestURI
110 return p 118 return p
111 } 119 }
112 120
113 // DecodeJSON decodes JSON data from r to v. 121 // DecodeJSON decodes JSON data from r to v.
114 // Returns an error if it fails. 122 // Returns an error if it fails.
115 func DecodeJSON(r io.Reader, v interface{}) error { 123 func DecodeJSON(r io.Reader, v interface{}) error {
116 return json.NewDecoder(r).Decode(v) 124 return json.NewDecoder(r).Decode(v)
117 } 125 }
118 126
119 // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. 127 // InitPayloadsMetadata loads all payloads' information into 'metadata' variable.
120 func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { 128 func InitPayloadsMetadata(drv string, db *sql.DB, project string) error {
121 var err error 129 var err error
122 if drv != "ora" && drv != "mysql" { 130 if drv != "ora" && drv != "mysql" {
123 err = errors.New("driver not supported") 131 err = errors.New("driver not supported")
124 return err 132 return err
125 } 133 }
126 134
127 driver = drv 135 driver = drv
128 metadataDB = db 136 metadataDB = db
129 activeProject = project 137 activeProject = project
130 138
131 logger, err = gologger.New("metadata", gologger.MaxLogSize100KB) 139 logger, err = gologger.New("metadata", gologger.MaxLogSize100KB)
132 if err != nil { 140 if err != nil {
133 fmt.Printf("webutility: %s\n", err.Error()) 141 fmt.Printf("webutility: %s\n", err.Error())
134 } 142 }
135 143
136 mu.Lock() 144 mu.Lock()
137 defer mu.Unlock() 145 defer mu.Unlock()
138 err = initMetadata(project) 146 err = initMetadata(project)
139 if err != nil { 147 if err != nil {
140 return err 148 return err
141 } 149 }
142 inited = true 150 inited = true
143 151
144 return nil 152 return nil
145 } 153 }
146 154
147 func EnableHotloading(interval int) { 155 func EnableHotloading(interval int) {
148 if interval > 0 { 156 if interval > 0 {
149 go hotload(interval) 157 go hotload(interval)
150 } 158 }
151 } 159 }
152 160
153 func GetMetadataForAllEntities() map[string]Payload { 161 func GetMetadataForAllEntities() map[string]Payload {
154 return metadata 162 return metadata
155 } 163 }
156 164
157 func GetMetadataForEntity(t string) (Payload, bool) { 165 func GetMetadataForEntity(t string) (Payload, bool) {
158 p, ok := metadata[t] 166 p, ok := metadata[t]
159 return p, ok 167 return p, ok
160 } 168 }
161 169
162 func QueEntityModelUpdate(entityType string, v interface{}) { 170 func QueEntityModelUpdate(entityType string, v interface{}) {
163 updateQue[entityType], _ = json.Marshal(v) 171 updateQue[entityType], _ = json.Marshal(v)
164 } 172 }
165 173
166 func UpdateEntityModels(command string) (total, upd, add int, err error) { 174 func UpdateEntityModels(command string) (total, upd, add int, err error) {
167 if command != "force" && command != "missing" { 175 if command != "force" && command != "missing" {
168 return total, 0, 0, errors.New("webutility: unknown command: " + command) 176 return total, 0, 0, errors.New("webutility: unknown command: " + command)
169 } 177 }
170 178
171 if !inited { 179 if !inited {
172 return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried.") 180 return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried.")
173 } 181 }
174 182
175 total = len(updateQue) 183 total = len(updateQue)
176 184
177 toUpdate := make([]string, 0) 185 toUpdate := make([]string, 0)
178 toAdd := make([]string, 0) 186 toAdd := make([]string, 0)
179 187
180 for k, _ := range updateQue { 188 for k, _ := range updateQue {
181 if _, exists := metadata[k]; exists { 189 if _, exists := metadata[k]; exists {
182 if command == "force" { 190 if command == "force" {
183 toUpdate = append(toUpdate, k) 191 toUpdate = append(toUpdate, k)
184 } 192 }
185 } else { 193 } else {
186 toAdd = append(toAdd, k) 194 toAdd = append(toAdd, k)
187 } 195 }
188 } 196 }
189 197
190 var uStmt *sql.Stmt 198 var uStmt *sql.Stmt
191 if driver == "ora" { 199 if driver == "ora" {
192 uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") 200 uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2")
193 if err != nil { 201 if err != nil {
194 logger.Trace(err.Error()) 202 logger.Trace(err.Error())
195 return 203 return
196 } 204 }
197 } else if driver == "mysql" { 205 } else if driver == "mysql" {
198 uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") 206 uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?")
199 if err != nil { 207 if err != nil {
200 logger.Trace(err.Error()) 208 logger.Trace(err.Error())
201 return 209 return
202 } 210 }
203 } 211 }
204 for _, k := range toUpdate { 212 for _, k := range toUpdate {
205 _, err = uStmt.Exec(string(updateQue[k]), k) 213 _, err = uStmt.Exec(string(updateQue[k]), k)
206 if err != nil { 214 if err != nil {
207 logger.Trace(err.Error()) 215 logger.Trace(err.Error())
208 return 216 return
209 } 217 }
210 upd++ 218 upd++
211 } 219 }
212 220
213 blankPayload, _ := json.Marshal(Payload{}) 221 blankPayload, _ := json.Marshal(Payload{})
214 var iStmt *sql.Stmt 222 var iStmt *sql.Stmt
215 if driver == "ora" { 223 if driver == "ora" {
216 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)") 224 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)")
217 if err != nil { 225 if err != nil {
218 logger.Trace(err.Error()) 226 logger.Trace(err.Error())
219 return 227 return
220 } 228 }
221 } else if driver == "mysql" { 229 } else if driver == "mysql" {
222 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)") 230 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)")
223 if err != nil { 231 if err != nil {
224 logger.Trace(err.Error()) 232 logger.Trace(err.Error())
225 return 233 return
226 } 234 }
227 } 235 }
228 for _, k := range toAdd { 236 for _, k := range toAdd {
229 _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) 237 _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k]))
230 if err != nil { 238 if err != nil {
231 logger.Trace(err.Error()) 239 logger.Trace(err.Error())
232 return 240 return
233 } 241 }
234 metadata[k] = Payload{} 242 metadata[k] = Payload{}
235 add++ 243 add++
236 } 244 }
237 245
238 return total, upd, add, nil 246 return total, upd, add, nil
239 } 247 }
240 248
241 func initMetadata(project string) error { 249 func initMetadata(project string) error {
242 rows, err := metadataDB.Query(`select 250 rows, err := metadataDB.Query(`select
243 entity_type, 251 entity_type,
244 metadata 252 metadata
245 from entities 253 from entities
246 where projekat = ` + fmt.Sprintf("'%s'", project)) 254 where projekat = ` + fmt.Sprintf("'%s'", project))
247 if err != nil { 255 if err != nil {
248 return err 256 return err
249 } 257 }
250 defer rows.Close() 258 defer rows.Close()
251 259
252 if len(metadata) > 0 { 260 if len(metadata) > 0 {
253 metadata = nil 261 metadata = nil
254 } 262 }
255 metadata = make(map[string]Payload) 263 metadata = make(map[string]Payload)
256 for rows.Next() { 264 for rows.Next() {
257 var name, load string 265 var name, load string
258 rows.Scan(&name, &load) 266 rows.Scan(&name, &load)
259 267
260 p := Payload{} 268 p := Payload{}
261 err := json.Unmarshal([]byte(load), &p) 269 err := json.Unmarshal([]byte(load), &p)
262 if err != nil { 270 if err != nil {
263 logger.Log("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load) 271 logger.Log("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load)
264 } else { 272 } else {
265 metadata[name] = p 273 metadata[name] = p
266 } 274 }
267 } 275 }
268 276
269 return nil 277 return nil
270 } 278 }
271 279
280 // TODO(marko):
281 //
282 // Currently supports only one hardcoded language...
283 //
284 //
285 //
286 //
287 //
288 // Metadata file ecpected format:
289 //
290 // [ payload A identifier ]
291 // key1 : value1
292 // key2 : value2
293 // ...
294 // [ payload B identifier ]
295 // key1 : value1
296 // key2 : value2
297 // ...
272 func LoadMetadataFromFile(path string) error { 298 func LoadMetadataFromFile(path string) error {
273 lines, err := ReadFileLines(path) 299 lines, err := ReadFileLines(path)
274 if err != nil { 300 if err != nil {
275 return err 301 return err
276 } 302 }
277 303
278 if len(metadata) > 0 {
279 metadata = nil
280 }
281 metadata = make(map[string]Payload) 304 metadata = make(map[string]Payload)
282 305
283 var name string 306 var name string
284 for i, l := range lines { 307 for i, l := range lines {
285 fmt.Println(l) 308 // skip empty lines
286 l = strings.TrimSpace(l) 309 if l = trimSpaces(l); len(l) == 0 {
287 if len(l) == 0 {
288 continue 310 continue
289 } 311 }
290 312
291 if strings.HasPrefix(l, "[") { 313 if isWrappedWith(l, "[", "]") {
292 name = strings.Trim(l, "[]") 314 name = strings.Trim(l, "[]")
293 metadata[name] = Payload{ 315 p := Payload{}
294 Lang: []Translation{ 316 p.addLang("sr", make(map[string]string))
295 Translation{ 317 metadata[name] = p
296 Language: "sr",
297 FieldsLabels: make(map[string]string),
298 },
299 },
300 }
301 continue 318 continue
302 } 319 }
303 320
304 if name == "" { 321 if name == "" {
305 return fmt.Errorf("webutility: LoadMetadataFromFile error on line %d: [no header][%s]\n", i+1, l) 322 return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [no header] [%s]\n", i+1, l)
306 } 323 }
307 324
308 parts := strings.Split(l, ":") 325 parts := strings.Split(l, ":")
309 if len(parts) < 2 { 326 if len(parts) != 2 {
310 return fmt.Errorf("webutility: LoadMetadataFromFile error on line %d: [invalid format][%s]\n", i+1, l) 327 return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [invalid format] [%s]\n", i+1, l)
311 } 328 }
312 329
313 k := strings.TrimSpace(parts[0]) 330 k := trimSpaces(parts[0])
314 v := strings.TrimSpace(parts[1]) 331 v := trimSpaces(parts[1])
315 if v != "-" { 332 if v != "-" {
316 metadata[name].Lang[0].FieldsLabels[k] = v 333 metadata[name].Lang[0].FieldsLabels[k] = v
317 } 334 }
318 } 335 }
319 336
320 return nil 337 return nil
321 } 338 }
322 339
340 func isWrappedWith(src, begin, end string) bool {
341 return strings.HasPrefix(src, begin) && strings.HasSuffix(src, end)
342 }
343
344 func trimSpaces(s string) string {
345 return strings.TrimSpace(s)
346 }
347
323 func ReadFileLines(path string) ([]string, error) { 348 func ReadFileLines(path string) ([]string, error) {
324 f, err := os.Open(path) 349 f, err := os.Open(path)
325 if err != nil { 350 if err != nil {
326 return nil, err 351 return nil, err
327 } 352 }
328 defer f.Close() 353 defer f.Close()
329 354
330 var s strings.Builder 355 var s strings.Builder
331 356
332 if _, err = io.Copy(&s, f); err != nil { 357 if _, err = io.Copy(&s, f); err != nil {
333 return nil, err 358 return nil, err
334 } 359 }
335 360
336 lines := strings.Split(s.String(), "\n") 361 lines := strings.Split(s.String(), "\n")
337 362
338 return lines, nil 363 return lines, nil
339 } 364 }
340 365
341 func hotload(n int) { 366 func hotload(n int) {
342 entityScan := make(map[string]int64) 367 entityScan := make(map[string]int64)
343 firstCheck := true 368 firstCheck := true
344 for { 369 for {
345 time.Sleep(time.Duration(n) * time.Second) 370 time.Sleep(time.Duration(n) * time.Second)
346 rows, err := metadataDB.Query(`select 371 rows, err := metadataDB.Query(`select
347 ora_rowscn, 372 ora_rowscn,
348 entity_type 373 entity_type
349 from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) 374 from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject))
350 if err != nil { 375 if err != nil {
351 logger.Log("webutility: hotload failed: %v\n", err) 376 logger.Log("webutility: hotload failed: %v\n", err)
352 time.Sleep(time.Duration(n) * time.Second) 377 time.Sleep(time.Duration(n) * time.Second)
353 continue 378 continue
354 } 379 }
355 380
356 var toRefresh []string 381 var toRefresh []string
357 for rows.Next() { 382 for rows.Next() {
358 var scanID int64 383 var scanID int64
359 var entity string 384 var entity string
360 rows.Scan(&scanID, &entity) 385 rows.Scan(&scanID, &entity)
361 oldID, ok := entityScan[entity] 386 oldID, ok := entityScan[entity]
362 if !ok || oldID != scanID { 387 if !ok || oldID != scanID {
363 entityScan[entity] = scanID 388 entityScan[entity] = scanID
364 toRefresh = append(toRefresh, entity) 389 toRefresh = append(toRefresh, entity)
365 } 390 }
366 } 391 }
367 rows.Close() 392 rows.Close()
368 393
369 if rows.Err() != nil { 394 if rows.Err() != nil {
370 logger.Log("webutility: hotload rset error: %v\n", rows.Err()) 395 logger.Log("webutility: hotload rset error: %v\n", rows.Err())
371 time.Sleep(time.Duration(n) * time.Second) 396 time.Sleep(time.Duration(n) * time.Second)
372 continue 397 continue
373 } 398 }
374 399
375 if len(toRefresh) > 0 && !firstCheck { 400 if len(toRefresh) > 0 && !firstCheck {
376 mu.Lock() 401 mu.Lock()
377 refreshMetadata(toRefresh) 402 refreshMetadata(toRefresh)
378 mu.Unlock() 403 mu.Unlock()
379 } 404 }
380 if firstCheck { 405 if firstCheck {
381 firstCheck = false 406 firstCheck = false
382 } 407 }
383 } 408 }
384 } 409 }
385 410
386 func refreshMetadata(entities []string) { 411 func refreshMetadata(entities []string) {
387 for _, e := range entities { 412 for _, e := range entities {
388 fmt.Printf("refreshing %s\n", e) 413 fmt.Printf("refreshing %s\n", e)
389 rows, err := metadataDB.Query(`select 414 rows, err := metadataDB.Query(`select
390 metadata 415 metadata
391 from entities 416 from entities
392 where projekat = ` + fmt.Sprintf("'%s'", activeProject) + 417 where projekat = ` + fmt.Sprintf("'%s'", activeProject) +
393 ` and entity_type = ` + fmt.Sprintf("'%s'", e)) 418 ` and entity_type = ` + fmt.Sprintf("'%s'", e))
394 419
395 if err != nil { 420 if err != nil {
396 logger.Log("webutility: refresh: prep: %v\n", err) 421 logger.Log("webutility: refresh: prep: %v\n", err)
397 rows.Close() 422 rows.Close()
398 continue 423 continue
399 } 424 }
400 425
401 for rows.Next() { 426 for rows.Next() {
402 var load string 427 var load string
403 rows.Scan(&load) 428 rows.Scan(&load)
404 p := Payload{} 429 p := Payload{}
405 err := json.Unmarshal([]byte(load), &p) 430 err := json.Unmarshal([]byte(load), &p)
406 if err != nil { 431 if err != nil {
407 logger.Log("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) 432 logger.Log("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load)
408 } else { 433 } else {
409 metadata[e] = p 434 metadata[e] = p
410 } 435 }
411 } 436 }
412 rows.Close() 437 rows.Close()
413 } 438 }
414 } 439 }
415 440
416 /* 441 /*
417 func ModifyMetadataForEntity(entityType string, p *Payload) error { 442 func ModifyMetadataForEntity(entityType string, p *Payload) error {
418 md, err := json.Marshal(*p) 443 md, err := json.Marshal(*p)
419 if err != nil { 444 if err != nil {
420 return err 445 return err
421 } 446 }
422 447
423 mu.Lock() 448 mu.Lock()
424 defer mu.Unlock() 449 defer mu.Unlock()
425 _, err = metadataDB.PrepAndExe(`update entities set 450 _, err = metadataDB.PrepAndExe(`update entities set
426 metadata = :1 451 metadata = :1
427 where projekat = :2 452 where projekat = :2
428 and entity_type = :3`, 453 and entity_type = :3`,
429 string(md), 454 string(md),
430 activeProject, 455 activeProject,
431 entityType) 456 entityType)
432 if err != nil { 457 if err != nil {
433 return err 458 return err
434 } 459 }
435 return nil 460 return nil
436 } 461 }
437 462
438 func DeleteEntityModel(entityType string) error { 463 func DeleteEntityModel(entityType string) error {
439 _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) 464 _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType)