root/trunk/grails-app/controllers/dbnp/importer/ImporterController.groovy @ 1492

Revision 1492, 17.8 KB (checked in by t.w.abma@…, 3 years ago)

- corrected StringList? items in the mappings page are now also correctly removed (just like Ontology terms) from the failedcells flow variable

  • Property svn:keywords set to Rev Author Date
Line 
1package dbnp.importer
2
3import dbnp.studycapturing.*
4import org.dbnp.gdt.*
5import grails.converters.JSON
6import org.codehaus.groovy.grails.plugins.web.taglib.ValidationTagLib
7import grails.plugins.springsecurity.Secured
8
9/**
10 * Wizard Controller
11 *
12 * @author Jeroen Wesbeek
13 * @since 20101206
14 *
15 * Revision information:
16 * $Rev$
17 * $Author$
18 * $Date$
19 */
20@Secured(['IS_AUTHENTICATED_REMEMBERED'])
21class ImporterController {
22        // the pluginManager is used to check if the Grom
23        // plugin is available so we can 'Grom' development
24        // notifications to the unified notifications daemon
25        // (see http://www.grails.org/plugin/grom)
26        def pluginManager
27        def authenticationService
28        def fileService
29        def ImporterService
30        def validationTagLib = new ValidationTagLib()
31        def GdtService
32
33        /**
34         * index method, redirect to the webflow
35         * @void
36         */
37        def index = {
38                // Grom a development message
39                if (pluginManager.getGrailsPlugin('grom')) "redirecting into the webflow".grom()
40
41                /**
42                 * Do you believe it in your head?
43                 * I can go with the flow
44                 * Don't say it doesn't matter (with the flow) matter anymore
45                 * I can go with the flow (I can go)
46                 * Do you believe it in your head?
47                 */
48                redirect(action: 'pages')
49        }
50
51        /**
52         * WebFlow definition
53         * @void
54         */
55        def pagesFlow = {
56                // start the flow
57                onStart {
58                        // Grom a development message
59                        if (pluginManager.getGrailsPlugin('grom')) "entering the WebFlow".grom()
60
61                        // define variables in the flow scope which is availabe
62                        // throughout the complete webflow also have a look at
63                        // the Flow Scopes section on http://www.grails.org/WebFlow
64                        //
65                        // The following flow scope variables are used to generate
66                        // wizard tabs. Also see common/_tabs.gsp for more information
67                        flow.page = 0
68                        flow.pages = [
69                                [title: 'Import file'],
70                                [title: 'Assign properties'],
71                                [title: 'Check imported data'],
72                                //[title: 'Imported'],
73                                [title: 'Done']
74                        ]
75                        flow.cancel = true;
76                        flow.quickSave = true;
77
78                        success()
79                }
80
81                // render the main wizard page which immediately
82                // triggers the 'next' action (hence, the main
83                // page dynamically renders the study template
84                // and makes the flow jump to the study logic)
85                mainPage {
86                        render(view: "/importer/index")
87                        onRender {
88                                // Grom a development message
89                                if (pluginManager.getGrailsPlugin('grom')) "rendering the main Ajaxflow page (index.gsp)".grom()
90
91                                // let the view know we're in page 1
92                                flow.page = 1
93                                success()
94                        }
95                        on("next").to "pageOne"
96                }
97
98                // File import and entitie template selection page
99                pageOne {
100                        render(view: "_page_one")
101                        onRender {
102                                log.info ".entering import wizard"
103                                // Grom a development message
104                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial: pages/_page_one.gsp".grom()
105
106                                flow.page = 1
107                                flow.studies = Study.findAllWhere(owner: authenticationService.getLoggedInUser())
108
109                                success()
110                        }
111
112            on("refresh") {
113                flash.importer_params = params               
114                                success()
115                        }.to "pageOne"
116
117                        on("next") {
118                                flash.wizardErrors = [:]
119
120                                // Study selected?
121                                flow.importer_study = (params.study) ? Study.get(params.study.id.toInteger()) : null
122
123                                // Trying to import data into an existing study?
124                                if (flow.importer_study)
125                                        if (flow.importer_study.canWrite(authenticationService.getLoggedInUser())) {
126                                                if (fileImportPage(flow, params)) {
127                                                        success()
128                                                } else {
129                                                        log.error ".importer wizard not all fields are filled in"
130                                                        this.appendErrorMap(['error': "Not all fields are filled in, please fill in or select all fields"], flash.wizardErrors)
131                                                        error()
132                                                }
133                                        } else {
134                                                log.error ".importer wizard wrong permissions"
135                                                this.appendErrorMap(['error': "You don't have the right permissions"], flash.wizardErrors)
136
137                                                error()
138                                        }
139                                else {
140                                        if (fileImportPage(flow, params)) {
141                                                success()
142                                        } else {
143                                                log.error ".importer wizard not all fields are filled in"
144                                                this.appendErrorMap(['error': "Not all fields are filled in, please fill in or select all fields"], flash.wizardErrors)
145                                                error()
146                                        }
147                                }
148
149                                // put your bussiness logic (if applicable) in here
150                        }.to "pageTwo"
151                }
152
153                // Property to column assignment page
154                pageTwo {
155                        render(view: "_page_two")
156                        onRender {
157                                log.info ".import wizard properties page"
158                                // Grom a development message
159                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial: pages/_page_two.gsp".grom()
160
161                                flow.page = 2
162                                success()
163                        }
164                        on("next") {
165                                if (propertiesPage(flow, flash, params)) {
166                                        success()
167                                } else {
168                                        log.error ".import wizard, properties are set wrong"
169                                        error()
170                                }
171                        }.to "pageThree"
172                        on("previous").to "pageOne"
173                }
174
175                // Mapping page
176                pageThree {
177                        render(view: "_page_three")
178                        onRender {
179                                log.info ".import wizard mapping page"
180                                // Grom a development message
181                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_page_three.gsp".grom()
182
183                                flow.page = 3
184                                success()
185                        }
186                        on("refresh") {
187                                success()
188                        }.to "pageThree"
189                        on("next") {
190                                if (mappingsPage(flow, flash, params)) {
191                                        flow.page = 4
192                                        success()
193                                } else {
194                                        log.error ".import wizard mapping error, could not validate all entities"
195                                        error()
196                                }
197                        }.to "save"
198                        on("previous").to "pageTwo"
199                }
200
201                // Imported data overview page
202                pageFour {
203                        render(view: "_page_four")
204                        onRender {
205                                // Grom a development message
206                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_page_four.gsp".grom()
207
208                                flow.page = 4
209                                success()
210                        }
211                        on("next") {
212                                if (importedPage(flow, params)) {
213                                        flow.page = 4
214                                        success()
215                                } else {
216                                        log.error ".import wizard imported error, something went wrong showing the imported entities"
217                                        error()
218                                }
219                        }.to "save"
220                        on("previous").to "pageThree"
221                }
222
223                // Save the imported data
224                save {
225                        action {
226                                // here you can validate and save the
227                                // instances you have created in the
228                                // ajax flow.
229                                // Grom a development message
230                                if (pluginManager.getGrailsPlugin('grom')) ".persisting instances to the database...".grom()
231
232                                if (saveEntities(flow, params)) {
233                                        //if (ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata)) {
234                                        //log.error ".import wizard succesfully persisted all entities"
235                                        //def session = sessionFactory.getCurrentSession()
236                                        //session.clear()
237                                        success()
238                                } else {
239                                        log.error ".import wizard, could not save entities:\n" + e.dump()
240                                        flow.page = 4
241                                        error()
242                                }
243                        }
244                        on("error").to "error"
245                        on(Exception).to "error"
246                        on("success").to "finalPage"
247                }
248
249                // render errors
250                error {
251                        render(view: "_error")
252                        onRender {
253                                // Grom a development message
254                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_error.gsp".grom()
255
256                                // set page to 4 so that the navigation
257                                // works (it is disabled on the final page)
258                                flow.page = 4
259                        }
260                        on("next").to "save"
261                        on("previous").to "pageFour"
262                }
263
264                // last wizard page
265                finalPage {
266                        render(view: "_final_page")
267                        onRender {
268                                // Grom a development message
269                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_final_page.gsp".grom()
270                                success()
271                        }
272                        onEnd {
273                                // clean flow scope
274                                flow.clear()
275                        }
276                }
277        }
278
279        /**
280         * Return templates which belong to a certain entity type
281         *
282         * @param entity entity name string (Sample, Subject, Study et cetera)
283         * @return JSON object containing the found templates
284         */
285        def ajaxGetTemplatesByEntity = {
286                // fetch all templates for a specific entity
287                def templates = Template.findAllByEntity(GdtService.getInstanceByEntity(params.entity.decodeURL()))
288
289
290                // render as JSON
291                render templates as JSON
292        }
293
294        /**
295         * Handle the file import page.
296         *
297         * @param Map LocalAttributeMap (the flow scope)
298         * @param Map GrailsParameterMap (the flow parameters = form data)
299         * @returns boolean true if correctly validated, otherwise false
300         */
301        boolean fileImportPage(flow, params) {
302                def importedfile = fileService.get(params['importfile'])
303                //fileService.delete(YourFile)
304
305                if (params.entity && params.template_id && importedfile.exists()) {
306                        // create a workbook instance of the file
307                        session.importer_workbook = ImporterService.getWorkbook(new FileInputStream(importedfile))
308
309                        def selectedentities = []
310
311                        def entityName = GdtService.decryptEntity(params.entity.decodeURL())
312                        def entityClass = GdtService.getInstanceByEntityName(entityName)
313
314                        // Initialize some session variables
315                        //flow.importer_workbook = wb // workbook object must be serialized for this to work
316
317                        flow.importer_template_id = params.template_id
318                        flow.importer_sheetindex = params.sheetindex.toInteger() - 1 // 0 == first sheet
319                        flow.importer_datamatrix_start = params.datamatrix_start.toInteger() - 1 // 0 == first row
320                        flow.importer_headerrow = params.headerrow.toInteger()
321
322                        // Get the header from the Excel file using the arguments given in the first step of the wizard
323                        flow.importer_header = ImporterService.getHeader(session.importer_workbook,
324                                flow.importer_sheetindex,
325                                flow.importer_headerrow,
326                                flow.importer_datamatrix_start,
327                                entityClass)
328
329                        // Initialize 'selected entities', used to show entities above the columns
330                        flow.importer_header.each {
331                                selectedentities.add([name: entityName, columnindex: it.key.toInteger()])
332                        }
333
334                        flow.importer_selectedentities = selectedentities
335
336                        session.importer_datamatrix = ImporterService.getDatamatrix(
337                                session.importer_workbook, flow.importer_header,
338                                flow.importer_sheetindex,
339                                flow.importer_datamatrix_start,
340                                5)
341
342                        flow.importer_templates = Template.get(flow.importer_template_id)
343                        flow.importer_allfieldtypes = "true"
344
345                        return true
346                }
347        }
348
349        /**
350         * Handle the property mapping page.
351         *
352         * @param Map LocalAttributeMap (the flow scope)
353         * @param Map GrailsParameterMap (the flow parameters = form data)
354         * @returns boolean true if correctly validated, otherwise false
355         */
356        boolean propertiesPage(flow, flash, params) {
357        flash.wizardErrors = [:]
358       
359                // Find actual Template object from the chosen template name
360                def template = Template.get(flow.importer_template_id)
361
362                params.columnproperty.index.each { columnindex, property ->
363                        // Create an actual class instance of the selected entity with the selected template
364                        // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities
365                        def entityClass = Class.forName(flow.importer_header[columnindex.toInteger()].entity.getName(), true, this.getClass().getClassLoader())
366                        def entityObj = entityClass.newInstance(template: template)
367
368                        // Store the selected property for this column into the column map for the ImporterService
369                        flow.importer_header[columnindex.toInteger()].property = property
370
371                        // Look up the template field type of the target TemplateField and store it also in the map
372                        flow.importer_header[columnindex.toInteger()].templatefieldtype = entityObj.giveFieldType(property)
373
374                        // Is a "Don't import" property assigned to the column?
375                        flow.importer_header[columnindex.toInteger()].dontimport = (property == "dontimport") ? true : false
376
377                        //if it's an identifier set the mapping column true or false
378                        entityObj.giveFields().each {
379                                (it.preferredIdentifier && (it.name == property)) ? flow.importer_header[columnindex.toInteger()].identifier = true : false
380                        }
381                }
382
383                // Import the workbook and store the table with entity records and store the failed cells
384                def (table, failedcells) = ImporterService.importData(flow.importer_template_id,
385                        session.importer_workbook,
386                        flow.importer_sheetindex,
387                        flow.importer_datamatrix_start,
388                        flow.importer_header)
389
390                flow.importer_importeddata = table
391
392        // loop through all entities to validate them and add them to wizardErrors flash when invalid
393        table.each { record ->
394            record.each { entity ->
395                if (!entity.validate()) {
396                                this.appendErrors(entity, flash.wizardErrors, 'entity_' + entity.getIdentifier() + '_')
397                }
398            }
399        }
400
401        flow.importer_failedcells = failedcells
402
403                return true
404        }
405
406        /**
407         * Handle the mapping page.
408         *
409         * @param Map LocalAttributeMap (the flow scope)
410         * @param Map GrailsParameterMap (the flow parameters = form data)
411         * @returns boolean true if correctly validated, otherwise false
412         */
413        boolean mappingsPage(flow, flash, params) {
414                flash.wizardErrors = [:]
415                flow.importer_invalidentities = 0
416
417                flow.importer_importeddata.each { table ->
418                        table.each { entity ->
419                                def invalidfields = 0
420
421                                // Set the fields for this entity by retrieving values from the params
422                                entity.giveFields().each { field ->
423                                        // field of type ontology and value "#invalidterm"?
424                                        if (field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM &&
425                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] == "#invalidterm"
426                                        ) {
427                                                invalidfields++
428                                        } else
429                                        if (field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM &&
430                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] != "#invalidterm") {
431                                                if (entity) removeFailedCell(flow.importer_failedcells, entity, field)
432                        println "removedcellontology" + entity
433                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
434                                        }
435                                        else
436
437                    if (field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST &&
438                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] != "#invalidterm") {
439                                                if (entity) removeFailedCell(flow.importer_failedcells, entity, field)
440                        println "removedcellstringlist" + entity.getIdentifier()
441                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
442                    } else
443                    if (field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST &&
444                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] == "#invalidterm"
445                                        ) {
446                                                invalidfields++
447                                        } else
448
449                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
450                                }
451
452                                // Determine entity class and add a parent (defined as Study in first step of wizard)
453                                switch (entity.getClass()) {
454                                        case [Subject, Sample, Event]: entity.parent = flow.importer_study
455                                }
456
457                                // Try to validate the entity now all fields have been set
458                                if (!entity.validate() || invalidfields) {
459                                        flow.importer_invalidentities++
460
461                                        // add errors to map
462                                        this.appendErrors(entity, flash.wizardErrors, "entity_" + entity.getIdentifier() + "_")
463
464                                        entity.errors.getAllErrors().each() {
465                                                log.error ".import wizard imported validation error:" + it
466                                        }
467                                } else {
468                                        //removeFailedCell(flow.importer_failedcells, entity)
469                                } // end else if
470
471                        } // end of record
472                } // end of table
473
474                return (flow.importer_invalidentities == 0) ? true : false
475        } // end of method
476
477        /**
478         * @param failedcell failed ontology cells
479         * @param entity entity to remove from the failedcells list
480         */
481        def removeFailedCell(failedcells, entity, field) {
482                // Valid entity, remove it from failedcells
483        def entityidfield = "entity_" + entity.getIdentifier() + "_" + field.name.toLowerCase()
484
485                failedcells.each { record ->
486                        def tempimportcells = []
487
488
489                        record.importcells.each { cell ->               
490                                // remove the cell from the failed cells session
491                                if (cell.entityidentifier != entityidfield) {
492                                        //record.removeFromImportcells(cell)
493                                        tempimportcells.add(cell)
494                                }
495                        }
496
497                        record.importcells = tempimportcells
498                        // } // end of importcells
499                } // end of failedcells
500        }
501
502        /**
503         * Handle the imported entities page.
504         *
505         * @param Map LocalAttributeMap (the flow scope)
506         * @param Map GrailsParameterMap (the flow parameters = form data)
507         * @returns boolean true if correctly validated, otherwise false
508         */
509        boolean importedPage(flow, params) {
510                return true
511        }
512
513        boolean saveEntities(flow, params) {
514                //def (validatedSuccesfully, updatedEntities, failedToPersist) =
515                //try {
516                ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata, authenticationService, log)
517
518                //}
519                //catch (Exception e) {
520//                log.error ".import wizard saveEntities error\n" + e.dump()
521//            }
522
523                //flow.importer_validatedsuccesfully = validatedSuccesfully
524                //flow.importer_failedtopersist = failedToPersist
525                //flow.imported_updatedentities = updatedEntities
526                //flow.importer_totalrows = flow.importer_importeddata.size
527                //flow.importer_referer = ""
528
529                return true
530        }
531
532        /**
533         * append errors of a particular object to a map
534         * @param object
535         * @param map linkedHashMap
536         * @void
537         */
538        def appendErrors(object, map) {
539                this.appendErrorMap(getHumanReadableErrors(object), map)
540        }
541
542        def appendErrors(object, map, prepend) {
543                this.appendErrorMap(getHumanReadableErrors(object), map, prepend)
544        }
545
546        /**
547         * append errors of one map to another map
548         * @param map linkedHashMap
549         * @param map linkedHashMap
550         * @void
551         */
552        def appendErrorMap(map, mapToExtend) {
553                map.each() {key, value ->
554                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
555                }
556        }
557
558        def appendErrorMap(map, mapToExtend, prepend) {
559                map.each() {key, value ->
560                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
561                }
562        }
563
564        /**
565         * transform domain class validation errors into a human readable
566         * linked hash map
567         * @param object validated domain class
568         * @return object  linkedHashMap
569         */
570        def getHumanReadableErrors(object) {
571                def errors = [:]
572                object.errors.getAllErrors().each() { error ->
573                        // error.codes.each() { code -> println code }           
574
575                        // generally speaking g.message(...) should work,
576                        // however it fails in some steps of the wizard
577                        // (add event, add assay, etc) so g is not always
578                        // availably. Using our own instance of the
579                        // validationTagLib instead so it is always
580                        // available to us
581                        errors[error.getArguments()[0]] = validationTagLib.message(error: error)
582                }
583
584                return errors
585        }
586}
Note: See TracBrowser for help on using the browser.