Commit d76a611ee8682ffd0780369db49ccb271794c333

Authored by Marko Tikvić
Exists in master

Merge branch 'master' of http://git.to-net.rs/marko.tikvic/webutility

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