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

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

- Importer tests on hold due to missing parse() method in Template domain
- ImportMappings? and MappingColumns? are now persisted and can be loaded, todo: null-entities? and lazy loading issue

  • 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                flow.importer_fuzzymatching="false"
109
110                                success()
111                        }
112
113            on("refresh") {
114                flash.importer_params = params           
115                                success()
116                        }.to "pageOne"
117
118                        on("next") {
119                                flash.wizardErrors = [:]
120                //flash.importer_params = params
121
122                                // Study selected?
123                                flow.importer_study = (params.study) ? Study.get(params.study.id.toInteger()) : null
124
125                                // Trying to import data into an existing study?
126                                if (flow.importer_study)
127                                        if (flow.importer_study.canWrite(authenticationService.getLoggedInUser()))
128                                                fileImportPage(flow, flash, params) ? success() : error()                                       
129                                        else {
130                                                log.error ".importer wizard wrong permissions"
131                                                this.appendErrorMap(['error': "You don't have the right permissions"], flash.wizardErrors)
132                                                error()
133                                        }
134                                else {
135                                        fileImportPage(flow, flash, params) ? success() : error()
136                                }
137
138                                // put your bussiness logic (if applicable) in here
139                        }.to "pageTwo"
140                }
141
142                // Property to column assignment page
143                pageTwo {
144                        render(view: "_page_two")
145                        onRender {
146                                log.info ".import wizard properties page"
147                                // Grom a development message
148                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial: pages/_page_two.gsp".grom()
149
150                flow.importer_importmappings = ImportMapping.list()
151
152                                flow.page = 2
153                                success()
154                        }
155            on("refresh") {
156                flow.importer_importmappings = ImportMapping.list()
157
158                // a name was given to the current property mapping, try to store it
159                if (params.mappingname) {
160                    flash.importer_columnproperty = params.columnproperty
161                    propertiesSaveImportMappingPage(flow, flash, params)                   
162                } else // trying to load an existing import mapping
163                if (params.importmapping_id) {
164                    propertiesLoadImportMappingPage(flow, flash, params)
165                }
166
167
168               
169                if (params.fuzzymatching == "true")
170                    flow.importer_fuzzymatching="true" else
171                    flow.importer_fuzzymatching="false"
172
173                success()
174                        }.to "pageTwo"
175
176                        on("next") {
177                                if (propertiesPage(flow, flash, params)) {
178                                        success()
179                                } else {
180                                        log.error ".import wizard, properties are set wrong"
181                                        error()
182                                }
183                        }.to "pageThree"
184                        on("previous").to "pageOne"
185                }
186
187                // Mapping page
188                pageThree {
189                        render(view: "_page_three")
190                        onRender {
191                                log.info ".import wizard mapping page"
192                                // Grom a development message
193                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_page_three.gsp".grom()
194
195                                flow.page = 3
196                                success()
197                        }
198                        on("refresh") {
199                                success()
200                        }.to "pageThree"
201                        on("next") {
202                                if (mappingsPage(flow, flash, params)) {
203                                        flow.page = 4
204                                        success()
205                                } else {
206                                        log.error ".import wizard mapping error, could not validate all entities"
207                                        error()
208                                }
209                        }.to "save"
210                        on("previous").to "pageTwo"
211                }
212
213                // Imported data overview page
214                pageFour {
215                        render(view: "_page_four")
216                        onRender {
217                                // Grom a development message
218                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_page_four.gsp".grom()
219
220                                flow.page = 4
221                                success()
222                        }
223                        on("next") {
224                                if (importedPage(flow, params)) {
225                                        flow.page = 4
226                                        success()
227                                } else {
228                                        log.error ".import wizard imported error, something went wrong showing the imported entities"
229                                        error()
230                                }
231                        }.to "save"
232                        on("previous").to "pageThree"
233                }
234
235                // Save the imported data
236                save {
237                        action {
238                                // here you can validate and save the
239                                // instances you have created in the
240                                // ajax flow.
241                                // Grom a development message
242                                if (pluginManager.getGrailsPlugin('grom')) ".persisting instances to the database...".grom()
243
244                                if (saveEntities(flow, params)) {
245                                        //if (ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata)) {
246                                        //log.error ".import wizard succesfully persisted all entities"
247                                        //def session = sessionFactory.getCurrentSession()
248                                        //session.clear()
249                                        success()
250                                } else {
251                                        log.error ".import wizard, could not save entities:\n" + e.dump()
252                                        flow.page = 4
253                                        error()
254                                }
255                        }
256                        on("error").to "error"
257                        on(Exception).to "error"
258                        on("success").to "finalPage"
259                }
260
261                // render errors
262                error {
263                        render(view: "_error")
264                        onRender {
265                                // Grom a development message
266                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_error.gsp".grom()
267
268                                // set page to 4 so that the navigation
269                                // works (it is disabled on the final page)
270                                flow.page = 4
271                        }
272                        on("next").to "save"
273                        on("previous").to "pageFour"
274                }
275
276                // last wizard page
277                finalPage {
278                        render(view: "_final_page")
279                        onRender {
280                                // Grom a development message
281                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_final_page.gsp".grom()
282                                success()
283                        }
284                        onEnd {
285                                // clean flow scope
286                                flow.clear()
287                        }
288                }
289        }
290
291    def propertiesManager = {
292        render (view:"common/_propertiesManager")
293    }
294
295        /**
296         * Return templates which belong to a certain entity type
297         *
298         * @param entity entity name string (Sample, Subject, Study et cetera)
299         * @return JSON object containing the found templates
300         */
301        def ajaxGetTemplatesByEntity = {
302                // fetch all templates for a specific entity
303                def templates = Template.findAllByEntity(GdtService.getInstanceByEntity(params.entity.decodeURL()))
304
305
306                // render as JSON
307                render templates as JSON
308        }
309
310        /**
311         * Handle the file import page.
312         *
313         * @param Map LocalAttributeMap (the flow scope)
314         * @param Map GrailsParameterMap (the flow parameters = form data)
315         * @returns boolean true if correctly validated, otherwise false
316         */
317        boolean fileImportPage(flow, flash, params) {
318                def importedfile = fileService.get(params['importfile'])
319                //fileService.delete(YourFile)
320
321        if (importedfile.exists()) {
322            try {
323                session.importer_workbook = ImporterService.getWorkbook(new FileInputStream(importedfile))
324            } catch (Exception e) {
325                log.error ".importer wizard could not load file: " + e
326                this.appendErrorMap(['error': "Wrong file (format), the importer requires an Excel file as input"], flash.wizardErrors)
327                return false
328            }
329        }
330
331                if (params.entity && params.template_id) {           
332                       
333            try {
334                session.importer_workbook = ImporterService.getWorkbook(new FileInputStream(importedfile))
335            } catch (Exception e) {
336                log.error ".importer wizard could not load file: " + e
337                this.appendErrorMap(['error': "Excel file required as input"], flash.wizardErrors)
338                return false
339            }
340
341                        def selectedentities = []
342
343                        def entityName = GdtService.decryptEntity(params.entity.decodeURL())
344                        def entityClass = GdtService.getInstanceByEntityName(entityName)
345
346                        // Initialize some session variables
347                        //flow.importer_workbook = wb // workbook object must be serialized for this to work
348
349                        flow.importer_template_id = params.template_id
350                        flow.importer_sheetindex = params.sheetindex.toInteger() - 1 // 0 == first sheet
351                        flow.importer_datamatrix_start = params.datamatrix_start.toInteger() - 1 // 0 == first row
352                        flow.importer_headerrow = params.headerrow.toInteger()
353            flow.importer_entityclass = entityClass.getClass()
354
355                        // Get the header from the Excel file using the arguments given in the first step of the wizard
356                        flow.importer_header = ImporterService.getHeader(session.importer_workbook,
357                                flow.importer_sheetindex,
358                                flow.importer_headerrow,
359                                flow.importer_datamatrix_start,
360                                entityClass)
361
362                        session.importer_datamatrix = ImporterService.getDatamatrix(
363                                session.importer_workbook, flow.importer_header,
364                                flow.importer_sheetindex,
365                                flow.importer_datamatrix_start,
366                                5)
367
368                        flow.importer_templates = Template.get(flow.importer_template_id)
369                        flow.importer_allfieldtypes = "true"
370
371                        return true
372                }
373
374        log.error ".importer wizard not all fields are filled in"
375        this.appendErrorMap(['error': "Not all fields are filled in, please fill in or select all fields"], flash.wizardErrors)
376        return false
377        }
378
379    /**
380     * Load an existing import mapping
381     *
382     * @param Map LocalAttributeMap (the flow scope)
383         * @param Map GrailsParameterMap (the flow parameters = form data)
384         * @returns boolean true if correctly validated, otherwise false
385         */
386    boolean propertiesLoadImportMappingPage(flow, flash, params) {
387        def im = ImportMapping.get(params.importmapping_id.toInteger())
388
389        flow.importer_header.each {
390            println "original=" + it.dump()
391        }
392
393        im.mappingcolumns.each { mappingcolumn ->
394            flow.importer_header[mappingcolumn.index.toInteger()] = mappingcolumn
395            println "adjusted=" + mappingcolumn.dump()
396        }
397    }
398
399    /**
400     * Save the properties as an import mapping.
401     *
402     * @param Map LocalAttributeMap (the flow scope)
403         * @param Map GrailsParameterMap (the flow parameters = form data)
404         * @returns boolean true if correctly validated, otherwise false
405         */
406    boolean propertiesSaveImportMappingPage(flow, flash, params) {
407        flash.wizardErrors = [:]
408        def isPreferredIdentifier = false
409
410                // Find actual Template object from the chosen template name
411                def template = Template.get(flow.importer_template_id)
412       
413        // Create new ImportMapping instance and persist it
414        def im = new ImportMapping(name:params.mappingname, entity: flow.importer_entityclass, template:template).save()
415       
416
417                params.columnproperty.index.each { columnindex, property ->
418                        // Create an actual class instance of the selected entity with the selected template
419                        // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities                     
420                        def entityClass = GdtService.getInstanceByEntityName(flow.importer_header[columnindex.toInteger()].entity.getName())
421            def entityObj = entityClass.newInstance(template:template)
422
423            def dontimport = (property == "dontimport") ? true : false
424
425            // Loop through all fields and find the preferred identifier
426            entityObj.giveFields().each {               
427                                isPreferredIdentifier = (it.preferredIdentifier && (it.name == property)) ? true : false
428                        }
429
430            // Create new MappingColumn instance
431            def mc = new MappingColumn (name: flow.importer_header[columnindex.toInteger()].name,
432                                        property:property,
433                                        index:columnindex,
434                                        entity:flow.importer_header[columnindex.toInteger()].entity,
435                                        templatefieldtype:entityObj.giveFieldType(property),
436                                        dontimport: dontimport,
437                                        identifier:isPreferredIdentifier)
438
439            // Save mappingcolumn
440            if (mc.validate()) {
441                im.addToMappingcolumns(mc)
442            }
443            else {
444                mc.errors.allErrors.each {
445                    println it
446                }
447            }
448
449            // Save importmapping
450            if (im.validate()) {
451                im.save(flush:true)
452            }
453            else {
454                im.errors.allErrors.each {
455                    println it
456                }
457            }
458           
459                }
460    }
461
462    /*def importmappings = {
463       
464        ImportMapping.list().each { importmapping ->
465            importmapping.mappingcolumns.each { mappingcolumn ->
466                println "das"+mappingcolumn.dump()
467
468            }
469        }
470
471        render("leeg")
472    }*/
473
474        /**
475         * Handle the property mapping page.
476         *
477         * @param Map LocalAttributeMap (the flow scope)
478         * @param Map GrailsParameterMap (the flow parameters = form data)
479         * @returns boolean true if correctly validated, otherwise false
480         */
481        boolean propertiesPage(flow, flash, params) {
482        flash.wizardErrors = [:]
483       
484                // Find actual Template object from the chosen template name
485                def template = Template.get(flow.importer_template_id)
486
487                params.columnproperty.index.each { columnindex, property ->
488                        // Create an actual class instance of the selected entity with the selected template
489                        // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities
490                        def entityClass = Class.forName(flow.importer_header[columnindex.toInteger()].entity.getName(), true, this.getClass().getClassLoader())
491                        def entityObj = entityClass.newInstance(template: template)
492
493                        // Store the selected property for this column into the column map for the ImporterService
494                        flow.importer_header[columnindex.toInteger()].property = property
495
496                        // Look up the template field type of the target TemplateField and store it also in the map
497                        flow.importer_header[columnindex.toInteger()].templatefieldtype = entityObj.giveFieldType(property)
498
499                        // Is a "Don't import" property assigned to the column?
500                        flow.importer_header[columnindex.toInteger()].dontimport = (property == "dontimport") ? true : false
501
502                        //if it's an identifier set the mapping column true or false
503                        entityObj.giveFields().each {
504                                (it.preferredIdentifier && (it.name == property)) ? flow.importer_header[columnindex.toInteger()].identifier = true : false
505                        }
506                }
507
508                // Import the workbook and store the table with entity records and store the failed cells
509                def (table, failedcells) = ImporterService.importData(flow.importer_template_id,
510                        session.importer_workbook,
511                        flow.importer_sheetindex,
512                        flow.importer_datamatrix_start,
513                        flow.importer_header)
514
515                flow.importer_importeddata = table
516
517        // loop through all entities to validate them and add them to wizardErrors flash when invalid
518        table.each { record ->
519            record.each { entity ->
520                if (!entity.validate()) {
521                                this.appendErrors(entity, flash.wizardErrors, 'entity_' + entity.getIdentifier() + '_')
522                }
523            }
524        }
525
526        flow.importer_failedcells = failedcells
527
528                return true
529        }
530
531        /**
532         * Handle the mapping page.
533         *
534         * @param Map LocalAttributeMap (the flow scope)
535         * @param Map GrailsParameterMap (the flow parameters = form data)
536         * @returns boolean true if correctly validated, otherwise false
537         */
538        boolean mappingsPage(flow, flash, params) {
539                flash.wizardErrors = [:]
540                flow.importer_invalidentities = 0
541
542                flow.importer_importeddata.each { table ->
543                        table.each { entity ->
544                                def invalidfields = 0
545
546                                // Set the fields for this entity by retrieving values from the params
547                                entity.giveFields().each { field ->
548                                        // field of type ontology and value "#invalidterm"?
549                                        if (field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM &&
550                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] == "#invalidterm"
551                                        ) {
552                                                invalidfields++
553                                        } else
554                                        if (field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM &&
555                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] != "#invalidterm") {
556                                                if (entity) removeFailedCell(flow.importer_failedcells, entity, field)                       
557                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
558                                        }
559                                        else
560
561                    if (field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST &&
562                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] != "#invalidterm") {
563                                                if (entity) removeFailedCell(flow.importer_failedcells, entity, field)                       
564                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
565                    } else
566                    if (field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST &&
567                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] == "#invalidterm"
568                                        ) {
569                                                invalidfields++
570                                        } else
571
572                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
573                                }
574
575                                // Determine entity class and add a parent (defined as Study in first step of wizard)
576                                switch (entity.getClass()) {
577                                        case [Subject, Sample, Event]: entity.parent = flow.importer_study
578                                }
579
580                                // Try to validate the entity now all fields have been set
581                                if (!entity.validate() || invalidfields) {
582                                        flow.importer_invalidentities++
583
584                                        // add errors to map
585                                        this.appendErrors(entity, flash.wizardErrors, "entity_" + entity.getIdentifier() + "_")
586
587                                        entity.errors.getAllErrors().each() {
588                                                log.error ".import wizard imported validation error:" + it
589                                        }
590                                } else {
591                                        //removeFailedCell(flow.importer_failedcells, entity)
592                                } // end else if
593
594                        } // end of record
595                } // end of table
596
597                return (flow.importer_invalidentities == 0) ? true : false
598        } // end of method
599
600        /**
601         * @param failedcell failed ontology cells
602         * @param entity entity to remove from the failedcells list
603         */
604        def removeFailedCell(failedcells, entity, field) {
605                // Valid entity, remove it from failedcells
606        def entityidfield = "entity_" + entity.getIdentifier() + "_" + field.name.toLowerCase()
607
608                failedcells.each { record ->
609                        def tempimportcells = []
610
611
612                        record.importcells.each { cell ->               
613                                // remove the cell from the failed cells session
614                                if (cell.entityidentifier != entityidfield) {
615                                        //record.removeFromImportcells(cell)
616                                        tempimportcells.add(cell)
617                                }
618                        }
619
620                        record.importcells = tempimportcells
621                        // } // end of importcells
622                } // end of failedcells
623        }
624
625        /**
626         * Handle the imported entities page.
627         *
628         * @param Map LocalAttributeMap (the flow scope)
629         * @param Map GrailsParameterMap (the flow parameters = form data)
630         * @returns boolean true if correctly validated, otherwise false
631         */
632        boolean importedPage(flow, params) {
633                return true
634        }
635
636        boolean saveEntities(flow, params) {
637                //def (validatedSuccesfully, updatedEntities, failedToPersist) =
638                //try {
639                ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata, authenticationService, log)
640
641                //}
642                //catch (Exception e) {
643//                log.error ".import wizard saveEntities error\n" + e.dump()
644//            }
645
646                //flow.importer_validatedsuccesfully = validatedSuccesfully
647                //flow.importer_failedtopersist = failedToPersist
648                //flow.imported_updatedentities = updatedEntities
649                //flow.importer_totalrows = flow.importer_importeddata.size
650                //flow.importer_referer = ""
651
652                return true
653        }
654
655        /**
656         * append errors of a particular object to a map
657         * @param object
658         * @param map linkedHashMap
659         * @void
660         */
661        def appendErrors(object, map) {
662                this.appendErrorMap(getHumanReadableErrors(object), map)
663        }
664
665        def appendErrors(object, map, prepend) {
666                this.appendErrorMap(getHumanReadableErrors(object), map, prepend)
667        }
668
669        /**
670         * append errors of one map to another map
671         * @param map linkedHashMap
672         * @param map linkedHashMap
673         * @void
674         */
675        def appendErrorMap(map, mapToExtend) {
676                map.each() {key, value ->
677                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
678                }
679        }
680
681        def appendErrorMap(map, mapToExtend, prepend) {
682                map.each() {key, value ->
683                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
684                }
685        }
686
687        /**
688         * transform domain class validation errors into a human readable
689         * linked hash map
690         * @param object validated domain class
691         * @return object  linkedHashMap
692         */
693        def getHumanReadableErrors(object) {
694                def errors = [:]
695                object.errors.getAllErrors().each() { error ->
696                        // error.codes.each() { code -> println code }           
697
698                        // generally speaking g.message(...) should work,
699                        // however it fails in some steps of the wizard
700                        // (add event, add assay, etc) so g is not always
701                        // availably. Using our own instance of the
702                        // validationTagLib instead so it is always
703                        // available to us
704                        errors[error.getArguments()[0]] = validationTagLib.message(error: error)
705                }
706
707                return errors
708        }
709}
Note: See TracBrowser for help on using the browser.