-
Notifications
You must be signed in to change notification settings - Fork 1
/
handlerUpdateWorkset.go
583 lines (511 loc) · 22.2 KB
/
handlerUpdateWorkset.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
// Copyright (c) 2016 OpenM++
// This code is licensed under the MIT license (see LICENSE.txt for details)
package main
import (
"encoding/csv"
"io"
"net/http"
"path"
"strings"
"github.com/openmpp/go/ompp/db"
"github.com/openmpp/go/ompp/omppLog"
)
// worksetReadonlyUpdateHandler update workset read-only status by model digest-or-name and workset name:
// POST /api/model/:model/workset/:set/readonly/:readonly
// If multiple models with same name exist then result is undefined.
// If no such workset exist in database then empty result returned.
func worksetReadonlyUpdateHandler(w http.ResponseWriter, r *http.Request) {
dn := getRequestParam(r, "model")
wsn := getRequestParam(r, "set")
// convert readonly flag
isReadonly, ok := getBoolRequestParam(r, "readonly")
if !ok {
http.Error(w, "Invalid value of workset read-only flag "+wsn, http.StatusBadRequest)
return
}
// update workset read-only status
digest, ws, ok := theCatalog.UpdateWorksetReadonly(dn, wsn, isReadonly)
if ok {
w.Header().Set("Content-Location", "/api/model/"+digest+"/workset/"+ws.Name)
} else {
ws = &db.WorksetRow{}
}
jsonResponse(w, r, ws)
}
// worksetCreateHandler creates new workset and append parameter(s) from json request:
// PUT /api/workset-create
// Json keys: model digest or name and workset name.
// If multiple models with same name exist then result is undefined, it is recommended to use model digest.
// If workset name is empty in json request then automatically generate unique workset name.
// If workset with same name already exist for that model then return error.
// Json content: workset "public" metadata and optioanl parameters list.
// Workset and parameters "public" metadata expected to be same as return of GET /api/model/:model/workset/:set/text/all
// Each parameter (if present) must contain "public" metadata and parameter values.
// Parameter values must include all values and can be either:
// JSON cell values, identical to output of read parameter "page": POST /api/model/:model/workset/:set/parameter/value
// or copy directions, for example run digest to copy value from.
// Dimension(s) and enum-based parameters expected to be enum codes, not enum id's.
func worksetCreateHandler(w http.ResponseWriter, r *http.Request) {
// decode json workset "public" metadata
var wp db.WorksetCreatePub
if !jsonRequestDecode(w, r, &wp) {
return // error at json decode, response done with http error
}
dn := wp.ModelDigest
if dn == "" {
dn = wp.ModelName
}
// if workset name is empty then automatically generate name
wsn := wp.Name
if wsn == "" {
ts, _ := theCatalog.getNewTimeStamp()
wsn = "set_" + ts
}
// return error if workset already exist or unable to get workset status
_, ok, notFound := theCatalog.WorksetStatus(dn, wsn)
if ok {
omppLog.Log("Error: workset already exist: " + wsn + " model: " + dn)
http.Error(w, "Error: workset already exist: "+wsn+" model: "+dn, http.StatusBadRequest)
return
}
if !notFound {
omppLog.Log("Failed to create workset: " + dn + " : " + wsn)
http.Error(w, "Failed to create workset: "+dn+" : "+wsn, http.StatusBadRequest)
return
}
// insert workset metadata with empty list of parameters and read-write status
newWp := db.WorksetPub{
WorksetHdrPub: db.WorksetHdrPub{
ModelName: wp.ModelName,
ModelDigest: wp.ModelDigest,
Name: wsn,
BaseRunDigest: wp.BaseRunDigest,
IsReadonly: false,
UpdateDateTime: wp.UpdateDateTime,
Txt: wp.Txt,
},
Param: []db.ParamRunSetPub{},
}
ok, _, wsRow, err := theCatalog.UpdateWorkset(true, &newWp)
if err != nil {
http.Error(w, "Failed create workset metadata "+dn+" : "+wsn+" : "+err.Error(), http.StatusBadRequest)
return
}
if !ok {
http.Error(w, "Failed create workset metadata "+dn+" : "+wsn, http.StatusBadRequest)
return
}
// append parameters metadata and values, each parameter will be inserted in separate transaction
for k := range wp.Param {
switch wp.Param[k].Kind {
case "run":
if e := theCatalog.CopyParameterToWsFromRun(dn, wsn, wp.Param[k].Name, false, wp.Param[k].From); e != nil {
http.Error(w, "Failed to copy parameter from model run"+wsn+" : "+wp.Param[k].Name+": "+wp.Param[k].From+" : "+e.Error(), http.StatusBadRequest)
return
}
continue
case "set":
if e := theCatalog.CopyParameterBetweenWs(dn, wsn, wp.Param[k].Name, false, wp.Param[k].From); e != nil {
http.Error(w, "Failed to copy parameter from workset "+wsn+" : "+wp.Param[k].Name+": "+wp.Param[k].From+" : "+e.Error(), http.StatusBadRequest)
return
}
continue
}
// default case: source of parameter must be a "value" or empty
if wp.Param[k].Kind != "" && wp.Param[k].Kind != "value" || len(wp.Param[k].Value) <= 0 {
http.Error(w, "Invalid (or empty) workset parameter values "+wsn+" : "+wp.Param[k].Name, http.StatusBadRequest)
return
}
if _, e := theCatalog.UpdateWorksetParameter(true, &newWp, &wp.Param[k].ParamRunSetPub, wp.Param[k].Value); e != nil {
http.Error(w, "Failed update workset parameter "+wsn+" : "+wp.Param[k].Name+" : "+e.Error(), http.StatusBadRequest)
return
}
}
// if required make workset read-only
if wp.IsReadonly {
theCatalog.UpdateWorksetReadonly(dn, wsn, wp.IsReadonly)
}
w.Header().Set("Content-Location", "/api/model/"+dn+"/workset/"+wsn) // respond with workset location
jsonResponse(w, r, wsRow)
}
// worksetReplaceHandler replace workset and all parameters from multipart-form:
// PUT /api/workset-replace
// Expected multipart form parts:
// first workset part with workset metadata in json
// and multiple parts file-csv=parameterName.csv.
// Json keys: model digest or name and workset name.
// Json content: workset "public" metadata.
// If multiple models with same name exist then result is undefined, it is recommended to use model digest.
// If workset name is empty in json request then automatically generate unique workset name.
// If no such workset exist in database then new workset created.
// If workset already exist then it is delete-insert operation:
// existing metadata, parameter list, parameter metadata and parameter values deleted from database
// and new metadata, parameters metadata and parameters values inserted.
func worksetReplaceHandler(w http.ResponseWriter, r *http.Request) {
worksetUpdateHandler(true, w, r)
}
// worksetMergeHandler merge workset metadata and parameters metadata and values from multipart-form:
// PATCH /api/workset-merge
// Expected multipart form parts:
// first workset part with workset metadata in json
// and optional multiple parts file-csv=parameterName.csv.
// Json keys: model digest or name and workset name.
// Json content: workset "public" metadata.
// If multiple models with same name exist then result is undefined, it is recommended to use model digest.
// If no such workset exist in database then new workset created.
// If workset name is empty in json request then automatically generate unique workset name.
// If workset already exist then merge operation existing workset metadata with new.
// If workset not exist then create new workset.
// Merge parameter list: if parameter exist then merge metadata.
// If new parameter values supplied then replace paramter values.
// If parameter not already exist in workset then parameter values must be supplied.
func worksetMergeHandler(w http.ResponseWriter, r *http.Request) {
worksetUpdateHandler(false, w, r)
}
// worksetUpdateHandler replace or merge workset metadata and parameters from multipart-form:
// Expected multipart form parts:
// first workset part with workset metadata in json
// and optional multiple parts file-csv=parameterName.csv.
// Json keys: model digest or name and workset name.
// Json content: workset "public" metadata.
// If parameter not already exist in workset then parameter values must be supplied.
// It is an error to add parameter metadata without parameter values.
func worksetUpdateHandler(isReplace bool, w http.ResponseWriter, r *http.Request) {
// parse multipart form: first part must be workset metadata
mr, err := r.MultipartReader()
if err != nil {
http.Error(w, "Error at multipart form open ", http.StatusBadRequest)
return
}
var newWp db.WorksetPub
if !jsonMultipartDecode(w, mr, "workset", &newWp) {
return // error at json decode, response done with http error
}
dn := newWp.ModelDigest
if dn == "" {
dn = newWp.ModelName
}
// if workset name is empty then automatically generate name
if newWp.Name == "" {
ts, _ := theCatalog.getNewTimeStamp()
newWp.Name = "set_" + ts
}
// get existing workset metadata
oldWp, _, err := theCatalog.WorksetTextFull(dn, newWp.Name, true, nil)
if err != nil {
http.Error(w, "Failed to get existing workset metadata "+dn+" : "+newWp.Name, http.StatusBadRequest)
return
}
// make starting list of parameters as new parameters which already exist in workset
newParamLst := append([]db.ParamRunSetPub{}, newWp.Param...)
newWp.Param = make([]db.ParamRunSetPub, 0, len(newParamLst))
for k := range newParamLst {
for j := range oldWp.Param {
// if this new parameter already exist in workset then keep it
if newParamLst[k].Name == oldWp.Param[j].Name {
newWp.Param = append(newWp.Param, newParamLst[k])
break
}
}
}
// update workset metadata, postpone read-only status until update completed
isReadonly := newWp.IsReadonly
newWp.IsReadonly = false
ok, _, wsRow, err := theCatalog.UpdateWorkset(isReplace, &newWp)
if err != nil {
http.Error(w, "Failed update workset metadata "+dn+" : "+newWp.Name+" : "+err.Error(), http.StatusBadRequest)
return
}
if !ok {
http.Error(w, "Failed update workset metadata "+dn+" : "+newWp.Name, http.StatusBadRequest)
return
}
// each parameter will be replaced in separate transaction
// decode multipart form csv files
pim := make(map[int]bool) // if index of parameter name in the map then csv supplied for that parameter
for {
part, err := mr.NextPart()
if err == io.EOF {
break // end of posted data
}
if err != nil {
http.Error(w, "Failed to get next part of multipart form "+dn+" : "+newWp.Name, http.StatusBadRequest)
return
}
// skip non parameter-csv data
if part.FormName() != "parameter-csv" {
part.Close()
continue
}
// validate: parameter name must be in the list of workset parameters
ext := path.Ext(part.FileName())
if ext != ".csv" {
part.Close()
http.Error(w, "Error: parameter file must have .csv extension "+newWp.Name+" : "+part.FileName(), http.StatusBadRequest)
return
}
name := strings.TrimSuffix(path.Base(part.FileName()), ext)
np := -1
for k := range newParamLst {
if name == newParamLst[k].Name {
np = k
break
}
}
if np < 0 {
part.Close()
http.Error(w, "Error: parameter must be in workset parameters list: "+newWp.Name+" : "+name, http.StatusBadRequest)
return
}
// read csv values and update parameter
csvRd := csv.NewReader(part)
csvRd.TrimLeadingSpace = true
csvRd.ReuseRecord = true
_, err = theCatalog.UpdateWorksetParameterCsv(isReplace, &newWp, &newParamLst[np], csvRd)
part.Close() // done with csv parameter data
if err != nil {
http.Error(w, "Failed update workset parameter "+newWp.Name+" : "+name+" : "+err.Error(), http.StatusBadRequest)
return
}
pim[np] = true // parameter metadata and csv values updated
}
// update parameter(s) metadata where parameter csv values not supplied
for k := range newParamLst {
if pim[k] {
continue // parameter metadata already updated together with csv parameter values
}
// update only parameter metadata
_, err = theCatalog.UpdateWorksetParameterCsv(isReplace, &newWp, &newParamLst[k], nil)
if err != nil {
http.Error(w, "Failed update workset parameter "+newWp.Name+" : "+newParamLst[k].Name+" : "+err.Error(), http.StatusBadRequest)
return
}
}
// if required make workset read-only
if isReadonly {
theCatalog.UpdateWorksetReadonly(dn, newWp.Name, isReadonly)
}
w.Header().Set("Content-Location", "/api/model/"+dn+"/workset/"+newWp.Name) // respond with workset location
jsonResponse(w, r, wsRow)
}
// worksetDeleteHandler delete workset and workset parameters:
// DELETE /api/model/:model/workset/:set
// If multiple models with same name exist then result is undefined.
// If no such workset exist in database then no error, empty operation.
func worksetDeleteHandler(w http.ResponseWriter, r *http.Request) {
dn := getRequestParam(r, "model")
wsn := getRequestParam(r, "set")
// update workset metadata
ok, err := theCatalog.DeleteWorkset(dn, wsn)
if err != nil {
http.Error(w, "Workset delete failed "+dn+": "+wsn, http.StatusBadRequest)
return
}
if ok {
w.Header().Set("Content-Location", "/api/model/"+dn+"/workset/"+wsn)
w.Header().Set("Content-Type", "text/plain")
}
}
// parameterPageUpdateHandler update a "page" of workset parameter values.
// PATCH /api/model/:model/workset/:set/parameter/:name/new/value
// Dimension(s) and enum-based parameters expected to be as enum codes.
// Input parameter "page" json expected to be identical to output of read parameter "page".
func parameterPageUpdateHandler(w http.ResponseWriter, r *http.Request) {
doUpdateParameterPageHandler(w, r, true)
}
// parameterIdPageUpdateHandler update a "page" of workset parameter values.
// PATCH /api/model/:model/workset/:set/parameter/:name/new/value-id
// Dimension(s) and enum-based parameters expected to be as enum id, not enum codes.
// Input parameter "page" json expected to be identical to output of read parameter "page".
func parameterIdPageUpdateHandler(w http.ResponseWriter, r *http.Request) {
doUpdateParameterPageHandler(w, r, false)
}
// doUpdateParameterPageHandler update a "page" of workset parameter values.
// Page is part of parameter values defined by zero-based "start" row number and row count.
// Dimension(s) and enum-based parameters can be as enum codes or enum id's.
func doUpdateParameterPageHandler(w http.ResponseWriter, r *http.Request, isCode bool) {
// url or query parameters
dn := getRequestParam(r, "model") // model digest-or-name
wsn := getRequestParam(r, "set") // workset name
name := getRequestParam(r, "name") // parameter name
var from func() (interface{}, error) = nil
// decode json body into array of cells
// make closure to process each cell, convert to id cells, if required
if !isCode {
var cArr []db.CellParam
if !jsonRequestDecode(w, r, &cArr) {
return // error at json decode, response done with http error
}
if len(cArr) <= 0 {
http.Error(w, "Workset parameter update failed "+wsn+" parameter empty: "+name, http.StatusInternalServerError)
return
}
// iterate over cell array
k := -1
from = func() (interface{}, error) {
k++
if k >= len(cArr) {
return nil, nil // end of data
}
return cArr[k], nil
}
} else {
// decode code cells from json body
var cArr []db.CellCodeParam
if !jsonRequestDecode(w, r, &cArr) {
return // error at json decode, response done with http error
}
if len(cArr) <= 0 {
http.Error(w, "Workset parameter update failed "+wsn+" parameter empty: "+name, http.StatusInternalServerError)
return
}
// convert from enum code cells to id cells
cvt, ok := theCatalog.ParameterCellConverter(true, dn, name)
if !ok {
http.Error(w, "Workset parameter update failed "+wsn+": "+name, http.StatusBadRequest)
return
}
// iterate over cell array and convert each cell from code to enum id
k := -1
from = func() (interface{}, error) {
k++
if k >= len(cArr) {
return nil, nil // end of data
}
c, err := cvt(cArr[k])
if err != nil {
return nil, err
}
return c, nil
}
}
// update parameter values
err := theCatalog.UpdateWorksetParameterPage(dn, wsn, name, from)
if err != nil {
omppLog.Log(err.Error())
http.Error(w, "Workset parameter update failed "+wsn+": "+name, http.StatusBadRequest)
return
}
w.Header().Set("Content-Location", "/api/model/"+dn+"/workset/"+wsn+"/parameter/"+name) // respond with workset parameter location
w.Header().Set("Content-Type", "text/plain")
}
// worksetParameterDeleteHandler delete workset parameter:
// DELETE /api/model/:model/workset/:set/parameter/:name
// If multiple models with same name exist then result is undefined.
// If no such parameter or workset exist in database then no error, empty operation.
func worksetParameterDeleteHandler(w http.ResponseWriter, r *http.Request) {
dn := getRequestParam(r, "model")
wsn := getRequestParam(r, "set")
name := getRequestParam(r, "name")
// delete workset parameter
ok, err := theCatalog.DeleteWorksetParameter(dn, wsn, name)
if err != nil {
http.Error(w, "Workset parameter delete failed "+wsn+": "+name, http.StatusBadRequest)
return
}
if ok {
w.Header().Set("Content-Location", "/api/model/"+dn+"/workset/"+wsn+"/parameter/"+name)
w.Header().Set("Content-Type", "text/plain")
}
}
// worksetParameterRunCopyHandler do copy (insert new) parameter into workset from model run:
// PUT /api/model/:model/workset/:set/copy/parameter/:name/from-run/:run
// If multiple models with same name exist then result is undefined.
// If such parameter already exist in destination workset then return error.
// Destination workset must be in read-write state.
// Run must be completed, run status one of: s=success, x=exit, e=error.
func worksetParameterRunCopyHandler(w http.ResponseWriter, r *http.Request) {
worksetParameterRunCopy(false, w, r)
}
// worksetParameterRunMergeHandler do merge (insert or update) parameter into workset from model run:
// PATCH /api/model/:model/workset/:set/merge/parameter/:name/from-run/:run
// If multiple models with same name exist then result is undefined.
// If such parameter already exist in destination workset
// then it is updated by delete old and insert new values and metadata.
// Destination workset must be in read-write state.
// Run must be completed, run status one of: s=success, x=exit, e=error.
func worksetParameterRunMergeHandler(w http.ResponseWriter, r *http.Request) {
worksetParameterRunCopy(true, w, r)
}
// worksetParameterRunCopy do copy parameter from model run into workset.
// if isReplace is true then it insert new else merge: insert new or update existing.
func worksetParameterRunCopy(isReplace bool, w http.ResponseWriter, r *http.Request) {
// url or query parameters
dn := getRequestParam(r, "model") // model digest-or-name
wsn := getRequestParam(r, "set") // workset name
name := getRequestParam(r, "name") // parameter name
rdsn := getRequestParam(r, "run") // source run digest or stamp or name
// copy workset parameter from model run
err := theCatalog.CopyParameterToWsFromRun(dn, wsn, name, isReplace, rdsn)
if err != nil {
omppLog.Log(err.Error())
http.Error(w, "Workset parameter copy failed "+wsn+": "+name+" from run: "+rdsn, http.StatusBadRequest)
return
}
w.Header().Set("Content-Location", "/api/model/"+dn+"/workset/"+wsn+"/parameter/"+name)
w.Header().Set("Content-Type", "text/plain")
}
// worksetParameterCopyFromWsHandler do copy (insert new) parameter from one workset to another:
// PUT /api/model/:model/workset/:set/copy/parameter/:name/from-workset/:from-set
// If multiple models with same name exist then result is undefined.
// If such parameter already exist in destination workset then return error.
// Destination workset must be in read-write state, source workset must be read-only.
func worksetParameterCopyFromWsHandler(w http.ResponseWriter, r *http.Request) {
worksetParameterCopyFromWs(false, w, r)
}
// worksetParameterMergeFromWsHandler do merge (insert or update) parameter from one workset to another:
// PATCH /api/model/:model/workset/:set/merge/parameter/:name/from-workset/:from-set
// If multiple models with same name exist then result is undefined.
// If such parameter already exist in destination workset
// then it is updated by delete old and insert new values and metadata.
// Destination workset must be in read-write state, source workset must be read-only.
func worksetParameterMergeFromWsHandler(w http.ResponseWriter, r *http.Request) {
worksetParameterCopyFromWs(true, w, r)
}
// worksetParameterCopyFromWs does copy parameter from one workset to another.
// if isReplace is true then it does insert new else merge: insert new or update existing.
func worksetParameterCopyFromWs(isReplace bool, w http.ResponseWriter, r *http.Request) {
// url or query parameters
dn := getRequestParam(r, "model") // model digest-or-name
dstWsName := getRequestParam(r, "set") // workset name
name := getRequestParam(r, "name") // parameter name
srcWsName := getRequestParam(r, "from-set") // source run digest or name
// copy workset parameter from other workset
err := theCatalog.CopyParameterBetweenWs(dn, dstWsName, name, isReplace, srcWsName)
if err != nil {
omppLog.Log(err.Error())
http.Error(w, "Workset parameter copy failed "+dstWsName+": "+name+" from run: "+srcWsName, http.StatusBadRequest)
return
}
w.Header().Set("Content-Location", "/api/model/"+dn+"/workset/"+dstWsName+"/parameter/"+name)
w.Header().Set("Content-Type", "text/plain")
}
// worksetParameterTextMergeHandler do merge (insert or update) workset parameter(s) value notes, array of parameters expected.
// PATCH /api/model/:model/workset/:set/parameter-text
// Model can be identified by digest or name and model run also identified by run digest-or-stamp-or-name.
// If multiple models with same name exist then result is undefined.
// If workset does not exist or is not in read-write state then return error.
// If parameter not exist in workset then return error.
// Input json must be array of ParamRunSetTxtPub,
// if parameters text array is empty then nothing updated, it is empty operation return is success
func worksetParameterTextMergeHandler(w http.ResponseWriter, r *http.Request) {
// url or query parameters
dn := getRequestParam(r, "model") // model digest-or-name
wsn := getRequestParam(r, "set") // workset name
// decode json parameter value notes
var pvtLst []db.ParamRunSetTxtPub
if !jsonRequestDecode(w, r, &pvtLst) {
return // error at json decode, response done with http error
}
// update workset parameter value notes in model catalog
ok, err := theCatalog.UpdateWorksetParameterText(dn, wsn, pvtLst)
if err != nil {
omppLog.Log(err.Error())
http.Error(w, "Workset parameter(s) value notes update failed "+dn+": "+wsn+": "+err.Error(), http.StatusBadRequest)
return
}
if ok {
w.Header().Set("Content-Location", "/api/model/"+dn+"/workset/"+wsn+"/parameter-text")
w.Header().Set("Content-Type", "text/plain")
}
}